chore: remove protocol recordHar option in favor of explicit harStart (#36030)

This commit is contained in:
Dmitry Gozman 2025-05-21 18:16:05 +00:00 committed by GitHub
parent 8168dc3496
commit 28e925c001
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 30 additions and 62 deletions

View File

@ -259,6 +259,7 @@ export class AndroidDevice extends ChannelOwner<channels.AndroidDeviceChannel> i
const result = await this._channel.launchBrowser(contextOptions);
const context = BrowserContext.from(result.context) as BrowserContext;
context._setOptions(contextOptions, {});
await context._initializeHarFromOptions(options.recordHar);
return context;
}

View File

@ -84,6 +84,7 @@ export class Browser extends ChannelOwner<channels.BrowserChannel> implements ap
const contextOptions = await prepareBrowserContextParams(this._platform, options);
const response = forReuse ? await this._channel.newContextForReuse(contextOptions) : await this._channel.newContext(contextOptions);
const context = BrowserContext.from(response.context);
await context._initializeHarFromOptions(options.recordHar);
await this._browserType._didCreateContext(context, contextOptions, this._options, options.logger || this._logger);
return context;
}

View File

@ -156,10 +156,19 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
]));
}
async _initializeHarFromOptions(recordHar: BrowserContextOptions['recordHar']) {
if (!recordHar)
return;
const defaultContent = recordHar.path.endsWith('.zip') ? 'attach' : 'embed';
await this._recordIntoHAR(recordHar.path, null, {
url: recordHar.urlFilter,
updateContent: recordHar.content ?? (recordHar.omitContent ? 'omit' : defaultContent),
updateMode: recordHar.mode ?? 'full',
});
}
_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 });
this.tracing._tracesDir = browserOptions.tracesDir;
}
@ -343,15 +352,17 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
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<void> {
async _recordIntoHAR(har: string, page: Page | null, options: { url?: string | RegExp, updateContent?: 'attach' | 'embed' | 'omit', updateMode?: 'minimal' | 'full'} = {}): Promise<void> {
const { harId } = await this._channel.harStart({
page: page?._channel,
options: prepareRecordHarOptions({
path: har,
options: {
zip: har.endsWith('.zip'),
content: options.updateContent ?? 'attach',
urlGlob: isString(options.url) ? options.url : undefined,
urlRegexSource: isRegExp(options.url) ? options.url.source : undefined,
urlRegexFlags: isRegExp(options.url) ? options.url.flags : undefined,
mode: options.updateMode ?? 'minimal',
urlFilter: options.url
})!
},
});
this._harRecorders.set(harId, { path: har, content: options.updateContent ?? 'attach' });
}
@ -512,19 +523,6 @@ async function prepareStorageState(platform: Platform, options: BrowserContextOp
}
}
function prepareRecordHarOptions(options: BrowserContextOptions['recordHar']): channels.RecordHarOptions | undefined {
if (!options)
return;
return {
path: options.path,
content: options.content || (options.omitContent ? 'omit' : undefined),
urlGlob: isString(options.urlFilter) ? options.urlFilter : undefined,
urlRegexSource: isRegExp(options.urlFilter) ? options.urlFilter.source : undefined,
urlRegexFlags: isRegExp(options.urlFilter) ? options.urlFilter.flags : undefined,
mode: options.mode
};
}
export async function prepareBrowserContextParams(platform: Platform, options: BrowserContextOptions): Promise<channels.BrowserNewContextParams> {
if (options.videoSize && !options.videosPath)
throw new Error(`"videoSize" option requires "videosPath" to be specified`);
@ -537,7 +535,6 @@ export async function prepareBrowserContextParams(platform: Platform, options: B
extraHTTPHeaders: options.extraHTTPHeaders ? headersObjectToArray(options.extraHTTPHeaders) : undefined,
storageState: await prepareStorageState(platform, options),
serviceWorkers: options.serviceWorkers,
recordHar: prepareRecordHarOptions(options.recordHar),
colorScheme: options.colorScheme === null ? 'no-override' : options.colorScheme,
reducedMotion: options.reducedMotion === null ? 'no-override' : options.reducedMotion,
forcedColors: options.forcedColors === null ? 'no-override' : options.forcedColors,

View File

@ -113,6 +113,7 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel> imple
return await this._wrapApiCall(async () => {
const result = await this._channel.launchPersistentContext(persistentParams);
const context = BrowserContext.from(result.context);
await context._initializeHarFromOptions(options.recordHar);
await this._didCreateContext(context, contextParams, options, logger);
return context;
});

View File

@ -60,6 +60,7 @@ export class Electron extends ChannelOwner<channels.ElectronChannel> implements
timeout: new TimeoutSettings(this._platform).launchTimeout(options),
};
const app = ElectronApplication.from((await this._channel.launch(params)).electronApplication);
await app._context._initializeHarFromOptions(options.recordHar);
app._context._setOptions(params, options);
return app;
}

View File

@ -193,7 +193,7 @@ scheme.SerializedError = tObject({
value: tOptional(tType('SerializedValue')),
});
scheme.RecordHarOptions = tObject({
path: tString,
zip: tOptional(tBoolean),
content: tOptional(tEnum(['embed', 'attach', 'omit'])),
mode: tOptional(tEnum(['full', 'minimal'])),
urlGlob: tOptional(tString),
@ -632,7 +632,6 @@ scheme.BrowserTypeLaunchPersistentContextParams = tObject({
height: tNumber,
})),
})),
recordHar: tOptional(tType('RecordHarOptions')),
strictSelectors: tOptional(tBoolean),
serviceWorkers: tOptional(tEnum(['allow', 'block'])),
selectorEngines: tOptional(tArray(tType('SelectorEngine'))),
@ -721,7 +720,6 @@ scheme.BrowserNewContextParams = tObject({
height: tNumber,
})),
})),
recordHar: tOptional(tType('RecordHarOptions')),
strictSelectors: tOptional(tBoolean),
serviceWorkers: tOptional(tEnum(['allow', 'block'])),
selectorEngines: tOptional(tArray(tType('SelectorEngine'))),
@ -793,7 +791,6 @@ scheme.BrowserNewContextForReuseParams = tObject({
height: tNumber,
})),
})),
recordHar: tOptional(tType('RecordHarOptions')),
strictSelectors: tOptional(tBoolean),
serviceWorkers: tOptional(tEnum(['allow', 'block'])),
selectorEngines: tOptional(tArray(tType('SelectorEngine'))),
@ -2459,7 +2456,6 @@ scheme.ElectronLaunchParams = tObject({
ignoreHTTPSErrors: tOptional(tBoolean),
locale: tOptional(tString),
offline: tOptional(tBoolean),
recordHar: tOptional(tType('RecordHarOptions')),
recordVideo: tOptional(tObject({
dir: tString,
size: tOptional(tObject({
@ -2699,7 +2695,6 @@ scheme.AndroidDeviceLaunchBrowserParams = tObject({
height: tNumber,
})),
})),
recordHar: tOptional(tType('RecordHarOptions')),
strictSelectors: tOptional(tBoolean),
serviceWorkers: tOptional(tEnum(['allow', 'block'])),
selectorEngines: tOptional(tArray(tType('SelectorEngine'))),

View File

@ -109,10 +109,6 @@ export abstract class BrowserContext extends SdkObject {
this._selectors = new Selectors(options.selectorEngines || [], options.testIdAttributeName);
this.fetchRequest = new BrowserContextAPIRequestContext(this);
if (this._options.recordHar)
this._harRecorders.set('', new HarRecorder(this, null, this._options.recordHar));
this.tracing = new Tracing(this, browser.options.tracesDir);
this.clock = new Clock(this);
}

View File

@ -42,7 +42,7 @@ export class HarRecorder implements HarTracerDelegate {
constructor(context: BrowserContext, page: Page | null, options: channels.RecordHarOptions) {
this._artifact = new Artifact(context, path.join(context._browser.options.artifactsDir, `${createGuid()}.har`));
const urlFilterRe = options.urlRegexSource !== undefined && options.urlRegexFlags !== undefined ? new RegExp(options.urlRegexSource, options.urlRegexFlags) : undefined;
const expectsZip = options.path.endsWith('.zip');
const expectsZip = !!options.zip;
const content = options.content || (expectsZip ? 'attach' : 'embed');
this._tracer = new HarTracer(context, page, this, {
content,

View File

@ -106,6 +106,8 @@ export class HarTracer {
eventsHelper.addEventListener(this._context, BrowserContext.Events.RequestFulfilled, request => this._onRequestFulfilled(request)),
eventsHelper.addEventListener(this._context, BrowserContext.Events.RequestContinued, request => this._onRequestContinued(request)),
);
for (const page of this._context.pages())
this._createPageEntryIfNeeded(page);
}
}

View File

@ -324,7 +324,7 @@ export type SerializedError = {
};
export type RecordHarOptions = {
path: string,
zip?: boolean,
content?: 'embed' | 'attach' | 'omit',
mode?: 'full' | 'minimal',
urlGlob?: string,
@ -1045,7 +1045,6 @@ export type BrowserTypeLaunchPersistentContextParams = {
height: number,
},
},
recordHar?: RecordHarOptions,
strictSelectors?: boolean,
serviceWorkers?: 'allow' | 'block',
selectorEngines?: SelectorEngine[],
@ -1129,7 +1128,6 @@ export type BrowserTypeLaunchPersistentContextOptions = {
height: number,
},
},
recordHar?: RecordHarOptions,
strictSelectors?: boolean,
serviceWorkers?: 'allow' | 'block',
selectorEngines?: SelectorEngine[],
@ -1246,7 +1244,6 @@ export type BrowserNewContextParams = {
height: number,
},
},
recordHar?: RecordHarOptions,
strictSelectors?: boolean,
serviceWorkers?: 'allow' | 'block',
selectorEngines?: SelectorEngine[],
@ -1315,7 +1312,6 @@ export type BrowserNewContextOptions = {
height: number,
},
},
recordHar?: RecordHarOptions,
strictSelectors?: boolean,
serviceWorkers?: 'allow' | 'block',
selectorEngines?: SelectorEngine[],
@ -1387,7 +1383,6 @@ export type BrowserNewContextForReuseParams = {
height: number,
},
},
recordHar?: RecordHarOptions,
strictSelectors?: boolean,
serviceWorkers?: 'allow' | 'block',
selectorEngines?: SelectorEngine[],
@ -1456,7 +1451,6 @@ export type BrowserNewContextForReuseOptions = {
height: number,
},
},
recordHar?: RecordHarOptions,
strictSelectors?: boolean,
serviceWorkers?: 'allow' | 'block',
selectorEngines?: SelectorEngine[],
@ -4329,7 +4323,6 @@ export type ElectronLaunchParams = {
ignoreHTTPSErrors?: boolean,
locale?: string,
offline?: boolean,
recordHar?: RecordHarOptions,
recordVideo?: {
dir: string,
size?: {
@ -4363,7 +4356,6 @@ export type ElectronLaunchOptions = {
ignoreHTTPSErrors?: boolean,
locale?: string,
offline?: boolean,
recordHar?: RecordHarOptions,
recordVideo?: {
dir: string,
size?: {
@ -4755,7 +4747,6 @@ export type AndroidDeviceLaunchBrowserParams = {
height: number,
},
},
recordHar?: RecordHarOptions,
strictSelectors?: boolean,
serviceWorkers?: 'allow' | 'block',
selectorEngines?: SelectorEngine[],
@ -4822,7 +4813,6 @@ export type AndroidDeviceLaunchBrowserOptions = {
height: number,
},
},
recordHar?: RecordHarOptions,
strictSelectors?: boolean,
serviceWorkers?: 'allow' | 'block',
selectorEngines?: SelectorEngine[],

View File

@ -324,7 +324,7 @@ SerializedError:
RecordHarOptions:
type: object
properties:
path: string
zip: boolean?
content:
type: enum?
literals:
@ -611,7 +611,6 @@ ContextOptions:
properties:
width: number
height: number
recordHar: RecordHarOptions?
strictSelectors: boolean?
serviceWorkers:
type: enum?
@ -3724,7 +3723,6 @@ Electron:
ignoreHTTPSErrors: boolean?
locale: string?
offline: boolean?
recordHar: RecordHarOptions?
recordVideo:
type: object?
properties:

View File

@ -42,11 +42,6 @@ async function pageWithHar(contextFactory: (options?: BrowserContextOptions) =>
};
}
it('should throw without path', async ({ browser }) => {
const error = await browser.newContext({ recordHar: {} as any }).catch(e => e);
expect(error.message).toContain('recordHar.path: expected string, got undefined');
});
it('should have version and creator', async ({ contextFactory, server }, testInfo) => {
const { page, getLog } = await pageWithHar(contextFactory, testInfo);
await page.goto(server.EMPTY_PAGE);
@ -90,17 +85,8 @@ it('should have pages in persistent context', async ({ launchPersistent, browser
await page.waitForLoadState('domcontentloaded');
await context.close();
const log = JSON.parse(fs.readFileSync(harPath).toString())['log'];
let pageEntry;
if (browserName === 'webkit') {
// Explicit locale emulation forces a new page creation when
// doing a new context.
// See https://github.com/microsoft/playwright/blob/13dd41c2e36a63f35ddef5dc5dec322052d670c6/packages/playwright-core/src/server/browserContext.ts#L232-L242
expect(log.pages.length).toBe(2);
pageEntry = log.pages[1];
} else {
expect(log.pages.length).toBe(1);
pageEntry = log.pages[0];
}
expect(log.pages.length).toBe(1);
const pageEntry = log.pages[0];
expect(pageEntry.id).toBeTruthy();
expect(pageEntry.title).toBe('Hello');
});