From 7e78f8e4b9a3b63edc3664ee2d993a5a4057a324 Mon Sep 17 00:00:00 2001 From: Mark Skelton Date: Fri, 15 Nov 2024 09:19:15 -0600 Subject: [PATCH] feat(routeFromHAR): add `shouldSave` option to control when to save HAR files Fixes #33559 --- docs/src/api/class-browsercontext.md | 5 +++ docs/src/api/class-page.md | 5 +++ docs/src/api/params.md | 1 + .../src/client/browserContext.ts | 23 +++++++++--- packages/playwright-core/src/client/page.ts | 2 +- packages/playwright-core/types/types.d.ts | 35 +++++++++++++++++++ tests/library/browsercontext-har.spec.ts | 28 ++++++++++++++- 7 files changed, 92 insertions(+), 7 deletions(-) diff --git a/docs/src/api/class-browsercontext.md b/docs/src/api/class-browsercontext.md index 8d6c57a3e3..ac970378a7 100644 --- a/docs/src/api/class-browsercontext.md +++ b/docs/src/api/class-browsercontext.md @@ -1267,6 +1267,11 @@ When set to `minimal`, only record information necessary for routing from HAR. T Optional setting to control resource content management. If `attach` is specified, resources are persisted as separate files or entries in the ZIP archive. If `embed` is specified, content is stored inline the HAR file. +### option: BrowserContext.routeFromHAR.shouldSave +* since: v1.49 +- `shouldSave` <[function]\(\):[boolean]> + +If specified, controls when the HAR file should be saved to disk. Defaults to always save the HAR file. ## async method: BrowserContext.routeWebSocket * since: v1.48 diff --git a/docs/src/api/class-page.md b/docs/src/api/class-page.md index f65a904932..b89e44291e 100644 --- a/docs/src/api/class-page.md +++ b/docs/src/api/class-page.md @@ -3668,6 +3668,11 @@ When set to `minimal`, only record information necessary for routing from HAR. T Optional setting to control resource content management. If `attach` is specified, resources are persisted as separate files or entries in the ZIP archive. If `embed` is specified, content is stored inline the HAR file. +### option: Page.routeFromHAR.shouldSave +* since: v1.49 +- `shouldSave` <[function]\(\):[boolean]> + +If specified, controls when the HAR file should be saved to disk. Defaults to always save the HAR file. ## async method: Page.routeWebSocket * since: v1.48 diff --git a/docs/src/api/params.md b/docs/src/api/params.md index 9b8d3de31b..8ed122d3c4 100644 --- a/docs/src/api/params.md +++ b/docs/src/api/params.md @@ -700,6 +700,7 @@ Logger sink for Playwright logging. - `path` <[path]> Path on the filesystem to write the HAR file to. If the file name ends with `.zip`, `content: 'attach'` is used by default. - `mode` ?<[HarMode]<"full"|"minimal">> When set to `minimal`, only record information necessary for routing from HAR. This omits sizes, timing, page, cookies, security and other types of HAR information that are not used when replaying from HAR. Defaults to `full`. - `urlFilter` ?<[string]|[RegExp]> A glob or regex pattern to filter requests that are stored in the HAR. When a [`option: Browser.newContext.baseURL`] via the context options was provided and the passed URL is a path, it gets merged via the [`new URL()`](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL) constructor. Defaults to none. + - `shouldSave` ?<[function]\(\):[boolean]> If specified, controls when the HAR file should be saved to disk. Defaults to always save the HAR file. Enables [HAR](http://www.softwareishard.com/blog/har-12-spec) recording for all pages into `recordHar.path` file. If not specified, the HAR is not recorded. Make sure to await [`method: BrowserContext.close`] for the HAR to be diff --git a/packages/playwright-core/src/client/browserContext.ts b/packages/playwright-core/src/client/browserContext.ts index 5ff432ec60..d49be6881e 100644 --- a/packages/playwright-core/src/client/browserContext.ts +++ b/packages/playwright-core/src/client/browserContext.ts @@ -64,7 +64,11 @@ export class BrowserContext extends ChannelOwner readonly _backgroundPages = new Set(); readonly _serviceWorkers = new Set(); readonly _isChromium: boolean; - private _harRecorders = new Map(); + private _harRecorders = new Map boolean) | undefined, + }>(); _closeWasCalled = false; private _closeReason: string | undefined; private _harRouters: HarRouter[] = []; @@ -152,8 +156,13 @@ export class BrowserContext extends ChannelOwner _setOptions(contextOptions: channels.BrowserNewContextParams, browserOptions: LaunchOptions) { this._options = contextOptions; - if (this._options.recordHar) - this._harRecorders.set('', { path: this._options.recordHar.path, content: this._options.recordHar.content }); + if (this._options.recordHar) { + this._harRecorders.set('', { + path: this._options.recordHar.path, + content: this._options.recordHar.content, + shouldSave: undefined, + }); + } this.tracing._tracesDir = browserOptions.tracesDir; } @@ -343,7 +352,7 @@ export class BrowserContext extends ChannelOwner await this._updateWebSocketInterceptionPatterns(); } - async _recordIntoHAR(har: string, page: Page | null, options: { url?: string | RegExp, notFound?: 'abort' | 'fallback', update?: boolean, updateContent?: 'attach' | 'embed', updateMode?: 'minimal' | 'full'} = {}): Promise { + async _recordIntoHAR(har: string, page: Page | null, options: { url?: string | RegExp, notFound?: 'abort' | 'fallback', update?: boolean, updateContent?: 'attach' | 'embed', updateMode?: 'minimal' | 'full', shouldSave?: () => boolean } = {}): Promise { const { harId } = await this._channel.harStart({ page: page?._channel, options: prepareRecordHarOptions({ @@ -353,7 +362,7 @@ export class BrowserContext extends ChannelOwner urlFilter: options.url })! }); - this._harRecorders.set(harId, { path: har, content: options.updateContent ?? 'attach' }); + this._harRecorders.set(harId, { path: har, content: options.updateContent ?? 'attach', shouldSave: options.shouldSave }); } async routeFromHAR(har: string, options: { url?: string | RegExp, notFound?: 'abort' | 'fallback', update?: boolean, updateContent?: 'attach' | 'embed', updateMode?: 'minimal' | 'full' } = {}): Promise { @@ -474,6 +483,10 @@ export class BrowserContext extends ChannelOwner await this._wrapApiCall(async () => { await this._browserType?._willCloseContext(this); for (const [harId, harParams] of this._harRecorders) { + const shouldSave = harParams.shouldSave ? harParams.shouldSave() : true; + if (!shouldSave) + continue; + const har = await this._channel.harExport({ harId }); const artifact = Artifact.from(har.artifact); // Server side will compress artifact if content is attach or if file is .zip. diff --git a/packages/playwright-core/src/client/page.ts b/packages/playwright-core/src/client/page.ts index f1d90fece2..a460ff1e7b 100644 --- a/packages/playwright-core/src/client/page.ts +++ b/packages/playwright-core/src/client/page.ts @@ -516,7 +516,7 @@ export class Page extends ChannelOwner implements api.Page await this._updateInterceptionPatterns(); } - async routeFromHAR(har: string, options: { url?: string | RegExp, notFound?: 'abort' | 'fallback', update?: boolean, updateContent?: 'attach' | 'embed', updateMode?: 'minimal' | 'full'} = {}): Promise { + async routeFromHAR(har: string, options: { url?: string | RegExp, notFound?: 'abort' | 'fallback', update?: boolean, updateContent?: 'attach' | 'embed', updateMode?: 'minimal' | 'full', shouldSave?: () => boolean } = {}): Promise { if (options.update) { await this._browserContext._recordIntoHAR(har, this, options); return; diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts index 85dd2d50a6..88b04a789f 100644 --- a/packages/playwright-core/types/types.d.ts +++ b/packages/playwright-core/types/types.d.ts @@ -4007,6 +4007,11 @@ export interface Page { */ notFound?: "abort"|"fallback"; + /** + * If specified, controls when the HAR file should be saved to disk. Defaults to always save the HAR file. + */ + shouldSave?: (() => boolean); + /** * If specified, updates the given HAR with the actual network information instead of serving from file. The file is * written to disk when @@ -9097,6 +9102,11 @@ export interface BrowserContext { */ notFound?: "abort"|"fallback"; + /** + * If specified, controls when the HAR file should be saved to disk. Defaults to always save the HAR file. + */ + shouldSave?: (() => boolean); + /** * If specified, updates the given HAR with the actual network information instead of serving from file. The file is * written to disk when @@ -9941,6 +9951,11 @@ export interface Browser { * [`new URL()`](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL) constructor. Defaults to none. */ urlFilter?: string|RegExp; + + /** + * If specified, controls when the HAR file should be saved to disk. Defaults to always save the HAR file. + */ + shouldSave?: (() => boolean); }; /** @@ -15024,6 +15039,11 @@ export interface BrowserType { * [`new URL()`](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL) constructor. Defaults to none. */ urlFilter?: string|RegExp; + + /** + * If specified, controls when the HAR file should be saved to disk. Defaults to always save the HAR file. + */ + shouldSave?: (() => boolean); }; /** @@ -16772,6 +16792,11 @@ export interface AndroidDevice { * [`new URL()`](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL) constructor. Defaults to none. */ urlFilter?: string|RegExp; + + /** + * If specified, controls when the HAR file should be saved to disk. Defaults to always save the HAR file. + */ + shouldSave?: (() => boolean); }; /** @@ -19195,6 +19220,11 @@ export interface Electron { * [`new URL()`](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL) constructor. Defaults to none. */ urlFilter?: string|RegExp; + + /** + * If specified, controls when the HAR file should be saved to disk. Defaults to always save the HAR file. + */ + shouldSave?: (() => boolean); }; /** @@ -22075,6 +22105,11 @@ export interface BrowserContextOptions { * [`new URL()`](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL) constructor. Defaults to none. */ urlFilter?: string|RegExp; + + /** + * If specified, controls when the HAR file should be saved to disk. Defaults to always save the HAR file. + */ + shouldSave?: (() => boolean); }; /** diff --git a/tests/library/browsercontext-har.spec.ts b/tests/library/browsercontext-har.spec.ts index 4a7834013a..d38effc370 100644 --- a/tests/library/browsercontext-har.spec.ts +++ b/tests/library/browsercontext-har.spec.ts @@ -556,4 +556,30 @@ it('should ignore aborted requests', async ({ contextFactory, server }) => { const result = await Promise.race([evalPromise, page2.waitForTimeout(1000).then(() => 'timeout')]); expect(result).toBe('timeout'); } -}); \ No newline at end of file +}); + +it('should save HAR files by default', async ({ contextFactory, server }, testInfo) => { + const harPath = testInfo.outputPath('har.har'); + const context = await contextFactory(); + await context.routeFromHAR(harPath, { update: true }); + + const page = await context.newPage(); + await page.goto(server.PREFIX + '/one-style.html'); + await context.close(); + + expect(fs.existsSync(harPath)).toBe(true); + const har = fs.readFileSync(harPath, 'utf-8'); + expect(har).not.toContain('background-color'); +}); + +it('can disable saving HAR files', async ({ contextFactory, server }, testInfo) => { + const harPath = testInfo.outputPath('har.har'); + const context = await contextFactory(); + await context.routeFromHAR(harPath, { update: true, shouldSave: () => false }); + + const page = await context.newPage(); + await page.goto(server.PREFIX + '/one-style.html'); + await context.close(); + + expect(fs.existsSync(harPath)).toBe(false); +});