Skip to content

Commit 1c8c1b4

Browse files
authored
chore: move agent cache management into ArtifactsRecorder (#38518)
1 parent c9bfd52 commit 1c8c1b4

File tree

8 files changed

+70
-46
lines changed

8 files changed

+70
-46
lines changed

packages/playwright-core/src/client/browser.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -74,11 +74,9 @@ export class Browser extends ChannelOwner<channels.BrowserChannel> implements ap
7474
await this._channel.disconnectFromReusedContext({ reason });
7575
}
7676

77-
async _innerNewContext(options: BrowserContextOptions = {}, forReuse: boolean): Promise<BrowserContext> {
78-
options = this._browserType._playwright.selectors._withSelectorOptions({
79-
...this._browserType._playwright._defaultContextOptions,
80-
...options,
81-
});
77+
async _innerNewContext(userOptions: BrowserContextOptions = {}, forReuse: boolean): Promise<BrowserContext> {
78+
const options = this._browserType._playwright.selectors._withSelectorOptions(userOptions);
79+
await this._instrumentation.runBeforeCreateBrowserContext(options);
8280
const contextOptions = await prepareBrowserContextParams(this._platform, options);
8381
const response = forReuse ? await this._channel.newContextForReuse(contextOptions) : await this._channel.newContext(contextOptions);
8482
const context = BrowserContext.from(response.context);

packages/playwright-core/src/client/browserType.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,13 +91,14 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel> imple
9191
}
9292

9393
async launchPersistentContext(userDataDir: string, options: LaunchPersistentContextOptions = {}): Promise<BrowserContext> {
94-
const logger = options.logger || this._playwright._defaultLaunchOptions?.logger;
9594
assert(!(options as any).port, 'Cannot specify a port without launching as a server.');
9695
options = this._playwright.selectors._withSelectorOptions({
9796
...this._playwright._defaultLaunchOptions,
98-
...this._playwright._defaultContextOptions,
9997
...options,
10098
});
99+
await this._instrumentation.runBeforeCreateBrowserContext(options);
100+
101+
const logger = options.logger || this._playwright._defaultLaunchOptions?.logger;
101102
const contextParams = await prepareBrowserContextParams(this._platform, options);
102103
const persistentParams: channels.BrowserTypeLaunchPersistentContextParams = {
103104
...contextParams,

packages/playwright-core/src/client/clientInstrumentation.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@
1515
*/
1616

1717
import type { BrowserContext } from './browserContext';
18-
import type { APIRequestContext } from './fetch';
18+
import type { APIRequestContext, NewContextOptions } from './fetch';
1919
import type { StackFrame } from '@protocol/channels';
2020
import type { Page } from './page';
21+
import type { BrowserContextOptions } from './types';
2122

2223
// Instrumentation can mutate the data, for example change apiName or stepId.
2324
export interface ApiCallData {
@@ -38,6 +39,8 @@ export interface ClientInstrumentation {
3839
onWillPause(options: { keepTestTimeout: boolean }): void;
3940
onPage(page: Page): void;
4041

42+
runBeforeCreateBrowserContext(options: BrowserContextOptions): Promise<void>;
43+
runBeforeCreateRequestContext(options: NewContextOptions): Promise<void>;
4144
runAfterCreateBrowserContext(context: BrowserContext): Promise<void>;
4245
runAfterCreateRequestContext(context: APIRequestContext): Promise<void>;
4346
runBeforeCloseBrowserContext(context: BrowserContext): Promise<void>;
@@ -49,6 +52,8 @@ export interface ClientInstrumentationListener {
4952
onApiCallEnd?(apiCall: ApiCallData): void;
5053
onWillPause?(options: { keepTestTimeout: boolean }): void;
5154
onPage?(page: Page): void;
55+
runBeforeCreateBrowserContext?(options: BrowserContextOptions): Promise<void>;
56+
runBeforeCreateRequestContext?(options: NewContextOptions): Promise<void>;
5257
runAfterCreateBrowserContext?(context: BrowserContext): Promise<void>;
5358
runAfterCreateRequestContext?(context: APIRequestContext): Promise<void>;
5459
runBeforeCloseBrowserContext?(context: BrowserContext): Promise<void>;

packages/playwright-core/src/client/fetch.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export type FetchOptions = {
4848
maxRetries?: number,
4949
};
5050

51-
type NewContextOptions = Omit<channels.PlaywrightNewRequestOptions, 'extraHTTPHeaders' | 'clientCertificates' | 'storageState' | 'tracesDir'> & {
51+
export type NewContextOptions = Omit<channels.PlaywrightNewRequestOptions, 'extraHTTPHeaders' | 'clientCertificates' | 'storageState' | 'tracesDir'> & {
5252
extraHTTPHeaders?: Headers,
5353
storageState?: string | SetStorageState,
5454
clientCertificates?: ClientCertificate[];
@@ -65,10 +65,8 @@ export class APIRequest implements api.APIRequest {
6565
}
6666

6767
async newContext(options: NewContextOptions & TimeoutOptions = {}): Promise<APIRequestContext> {
68-
options = {
69-
...this._playwright._defaultContextOptions,
70-
...options,
71-
};
68+
options = { ...options };
69+
await this._playwright._instrumentation.runBeforeCreateRequestContext(options);
7270
const storageState = typeof options.storageState === 'string' ?
7371
JSON.parse(await this._playwright._platform.fs().promises.readFile(options.storageState, 'utf8')) :
7472
options.storageState;

packages/playwright-core/src/client/playwright.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import { APIRequest } from './fetch';
2424
import { Selectors } from './selectors';
2525

2626
import type * as channels from '@protocol/channels';
27-
import type { BrowserContextOptions, LaunchOptions } from 'playwright-core';
27+
import type { LaunchOptions } from 'playwright-core';
2828

2929
export class Playwright extends ChannelOwner<channels.PlaywrightChannel> {
3030
readonly _android: Android;
@@ -39,7 +39,6 @@ export class Playwright extends ChannelOwner<channels.PlaywrightChannel> {
3939

4040
// Instrumentation.
4141
_defaultLaunchOptions?: LaunchOptions;
42-
_defaultContextOptions?: BrowserContextOptions;
4342
_defaultContextTimeout?: number;
4443
_defaultContextNavigationTimeout?: number;
4544

packages/playwright/src/index.ts

Lines changed: 47 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import type { ClientInstrumentationListener } from '../../playwright-core/src/cl
3131
import type { Playwright as PlaywrightImpl } from '../../playwright-core/src/client/playwright';
3232
import type { Browser as BrowserImpl } from '../../playwright-core/src/client/browser';
3333
import type { BrowserContext as BrowserContextImpl } from '../../playwright-core/src/client/browserContext';
34-
import type { APIRequestContext as APIRequestContextImpl } from '../../playwright-core/src/client/fetch';
34+
import type { APIRequestContext as APIRequestContextImpl, NewContextOptions as APIRequestContextOptions } from '../../playwright-core/src/client/fetch';
3535
import type { ChannelOwner } from '../../playwright-core/src/client/channelOwner';
3636
import type { Page as PageImpl } from '../../playwright-core/src/client/page';
3737
import type { BrowserContext, BrowserContextOptions, LaunchOptions, Page, Tracing } from 'playwright-core';
@@ -228,42 +228,33 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
228228
if (serviceWorkers !== undefined)
229229
options.serviceWorkers = serviceWorkers;
230230

231-
const workerFile = await agentCacheWorkerFile(agent, testInfo as TestInfoImpl);
232-
if (agent && workerFile)
233-
options.agent = { ...agent, cacheFile: workerFile };
234-
235231
await use({
236232
...contextOptions,
237233
...options,
238234
});
239-
240-
if (testInfo.status === 'passed' && workerFile)
241-
await (testInfo as TestInfoImpl)._upstreamStorage(workerFile);
242235
}, { box: true }],
243236

244-
_setupContextOptions: [async ({ playwright, _combinedContextOptions, actionTimeout, navigationTimeout, testIdAttribute }, use, testInfo) => {
237+
_setupContextOptions: [async ({ playwright, actionTimeout, navigationTimeout, testIdAttribute }, use, testInfo) => {
245238
if (testIdAttribute)
246239
playwrightLibrary.selectors.setTestIdAttribute(testIdAttribute);
247240
testInfo.snapshotSuffix = process.platform;
248241
if (debugMode() === 'inspector')
249242
(testInfo as TestInfoImpl)._setDebugMode();
250243

251-
playwright._defaultContextOptions = _combinedContextOptions;
252244
playwright._defaultContextTimeout = actionTimeout || 0;
253245
playwright._defaultContextNavigationTimeout = navigationTimeout || 0;
254246
await use();
255-
playwright._defaultContextOptions = undefined;
256247
playwright._defaultContextTimeout = undefined;
257248
playwright._defaultContextNavigationTimeout = undefined;
258249
}, { auto: 'all-hooks-included', title: 'context configuration', box: true } as any],
259250

260-
_setupArtifacts: [async ({ playwright, screenshot }, use, testInfo) => {
251+
_setupArtifacts: [async ({ playwright, screenshot, _combinedContextOptions, agent }, use, testInfo) => {
261252
// This fixture has a separate zero-timeout slot to ensure that artifact collection
262253
// happens even after some fixtures or hooks time out.
263254
// Now that default test timeout is known, we can replace zero with an actual value.
264255
testInfo.setTimeout(testInfo.project.timeout);
265256

266-
const artifactsRecorder = new ArtifactsRecorder(playwright, tracing().artifactsDir(), screenshot);
257+
const artifactsRecorder = new ArtifactsRecorder(playwright, tracing().artifactsDir(), screenshot, agent);
267258
await artifactsRecorder.willStartTest(testInfo as TestInfoImpl);
268259

269260
const tracingGroupSteps: TestStepInternal[] = [];
@@ -317,20 +308,33 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
317308
if (!keepTestTimeout)
318309
currentTestInfo()?._setDebugMode();
319310
},
311+
runBeforeCreateBrowserContext: async (options: BrowserContextOptions) => {
312+
for (const [key, value] of Object.entries(_combinedContextOptions)) {
313+
if (!(key in options))
314+
options[key as keyof BrowserContextOptions] = value;
315+
}
316+
await artifactsRecorder.willCreateBrowserContext(options);
317+
},
318+
runBeforeCreateRequestContext: async (options: APIRequestContextOptions) => {
319+
for (const [key, value] of Object.entries(_combinedContextOptions)) {
320+
if (!(key in options))
321+
options[key as keyof APIRequestContextOptions] = value;
322+
}
323+
},
320324
runAfterCreateBrowserContext: async (context: BrowserContextImpl) => {
321-
await artifactsRecorder?.didCreateBrowserContext(context);
325+
await artifactsRecorder.didCreateBrowserContext(context);
322326
const testInfo = currentTestInfo();
323327
if (testInfo)
324328
attachConnectedHeaderIfNeeded(testInfo, context.browser());
325329
},
326330
runAfterCreateRequestContext: async (context: APIRequestContextImpl) => {
327-
await artifactsRecorder?.didCreateRequestContext(context);
331+
await artifactsRecorder.didCreateRequestContext(context);
328332
},
329333
runBeforeCloseBrowserContext: async (context: BrowserContextImpl) => {
330-
await artifactsRecorder?.willCloseBrowserContext(context);
334+
await artifactsRecorder.willCloseBrowserContext(context);
331335
},
332336
runBeforeCloseRequestContext: async (context: APIRequestContextImpl) => {
333-
await artifactsRecorder?.willCloseRequestContext(context);
337+
await artifactsRecorder.willCloseRequestContext(context);
334338
},
335339
};
336340

@@ -643,10 +647,12 @@ class ArtifactsRecorder {
643647

644648
private _screenshotRecorder: SnapshotRecorder;
645649
private _pageSnapshot: string | undefined;
650+
private _agent: PlaywrightTestOptions['agent'];
646651

647-
constructor(playwright: PlaywrightImpl, artifactsDir: string, screenshot: ScreenshotOption) {
652+
constructor(playwright: PlaywrightImpl, artifactsDir: string, screenshot: ScreenshotOption, agent: PlaywrightTestOptions['agent']) {
648653
this._playwright = playwright;
649654
this._artifactsDir = artifactsDir;
655+
this._agent = agent;
650656
const screenshotOptions = typeof screenshot === 'string' ? undefined : screenshot;
651657
this._startedCollectingArtifacts = Symbol('startedCollectingArtifacts');
652658

@@ -673,10 +679,15 @@ class ArtifactsRecorder {
673679
await this._startTraceChunkOnContextCreation(context, context.tracing);
674680
}
675681

682+
async willCreateBrowserContext(options: BrowserContextOptions) {
683+
await this._cloneAgentCache(options);
684+
}
685+
676686
async willCloseBrowserContext(context: BrowserContextImpl) {
677687
await this._stopTracing(context, context.tracing);
678688
await this._screenshotRecorder.captureTemporary(context);
679689
await this._takePageSnapshot(context);
690+
await this._upstreamAgentCache(context);
680691
}
681692

682693
private async _takePageSnapshot(context: BrowserContextImpl) {
@@ -698,6 +709,24 @@ class ArtifactsRecorder {
698709
} catch {}
699710
}
700711

712+
private async _cloneAgentCache(options: BrowserContextOptions) {
713+
if (!this._agent || this._agent.cacheMode === 'ignore')
714+
return;
715+
if (!this._agent.cacheFile && !this._agent.cachePathTemplate)
716+
return;
717+
718+
const cacheFile = this._agent.cacheFile ?? this._testInfo._applyPathTemplate(this._agent.cachePathTemplate!, 'cache', '.json');
719+
const workerFile = await this._testInfo._cloneStorage(cacheFile);
720+
if (this._agent && workerFile)
721+
options.agent = { ...this._agent, cacheFile: workerFile };
722+
}
723+
724+
private async _upstreamAgentCache(context: BrowserContextImpl) {
725+
const agent = context._options.agent;
726+
if (this._testInfo.status === 'passed' && agent?.cacheFile)
727+
await this._testInfo._upstreamStorage(agent.cacheFile);
728+
}
729+
701730
async didCreateRequestContext(context: APIRequestContextImpl) {
702731
await this._startTraceChunkOnContextCreation(context, context._tracing);
703732
}
@@ -792,16 +821,6 @@ function tracing() {
792821
return (test.info() as TestInfoImpl)._tracing;
793822
}
794823

795-
async function agentCacheWorkerFile(agent: PlaywrightTestOptions['agent'], testInfo: TestInfoImpl): Promise<string | undefined> {
796-
if (!agent || agent.cacheMode === 'ignore')
797-
return undefined;
798-
if (!agent.cacheFile && !agent.cachePathTemplate)
799-
return undefined;
800-
801-
const cacheFile = agent.cacheFile ?? testInfo._applyPathTemplate(agent.cachePathTemplate!, 'cache', '.json');
802-
return await testInfo._cloneStorage(cacheFile);
803-
}
804-
805824
export const test = _baseTest.extend<TestFixtures, WorkerFixtures>(playwrightFixtures);
806825

807826
export { defineConfig } from './common/configLoader';

tests/library/browsercontext-reuse.spec.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ class LaunchScenario {
3737
const browser = await this.browser();
3838
if (this._context)
3939
await (browser as any)._disconnectFromReusedContext('reusedContext');
40-
const defaultContextOptions = (this._browserType as any)._playwright._defaultContextOptions;
40+
const defaultContextOptions = {};
41+
await (this._browserType as any)._instrumentation.runBeforeCreateBrowserContext(defaultContextOptions);
4142
this._context = await (browser as any)._newContextForReuse({ ...defaultContextOptions, ...options });
4243
return this._context;
4344
}
@@ -67,7 +68,8 @@ class ConnectScenario {
6768
if (this._browser)
6869
await this._browser.close();
6970
this._browser = await this._browserType.connect(server.wsEndpoint());
70-
const defaultContextOptions = (this._browserType as any)._playwright._defaultContextOptions;
71+
const defaultContextOptions = {};
72+
await (this._browserType as any)._instrumentation.runBeforeCreateBrowserContext(defaultContextOptions);
7173
return await (this._browser as any)._newContextForReuse({ ...defaultContextOptions, ...options });
7274
}
7375

tests/library/global-fetch.spec.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,9 @@ it('should set playwright as user-agent', async ({ playwright, server, isWindows
255255
});
256256

257257
it('should be able to construct with context options', async ({ playwright, browserType, server }) => {
258-
const request = await playwright.request.newContext((browserType as any)._playwright._defaultContextOptions);
258+
const defaultContextOptions = {};
259+
await (playwright as any)._instrumentation.runBeforeCreateRequestContext(defaultContextOptions);
260+
const request = await playwright.request.newContext(defaultContextOptions);
259261
const response = await request.get(server.EMPTY_PAGE);
260262
expect(response.ok()).toBeTruthy();
261263
await request.dispose();

0 commit comments

Comments
 (0)