move to serverside

This commit is contained in:
Simon Knott 2025-02-04 12:47:35 +01:00
parent 7a08cd6fa7
commit 5e49e08ba2
No known key found for this signature in database
GPG key ID: 8CEDC00028084AEC
11 changed files with 35 additions and 42 deletions

View file

@ -81,7 +81,7 @@ export class Browser extends ChannelOwner<channels.BrowserChannel> implements ap
async _innerNewContext(options: BrowserContextOptions = {}, forReuse: boolean): Promise<BrowserContext> {
options = { ...this._browserType._defaultContextOptions, ...options };
const contextOptions = await prepareBrowserContextParams(options);
const contextOptions = await prepareBrowserContextParams(options, this._browserType);
const response = forReuse ? await this._channel.newContextForReuse(contextOptions) : await this._channel.newContext(contextOptions);
const context = BrowserContext.from(response.context);
await this._browserType._didCreateContext(context, contextOptions, this._options, options.logger || this._logger);

View file

@ -29,8 +29,7 @@ import { Events } from './events';
import { TimeoutSettings } from '../common/timeoutSettings';
import { Waiter } from './waiter';
import type { Headers, WaitForEventOptions, BrowserContextOptions, StorageState, LaunchOptions } from './types';
import type { RegisteredListener } from '../utils';
import { type URLMatch, headersObjectToArray, isRegExp, isString, urlMatchesEqual, mkdirIfNeeded, eventsHelper } from '../utils';
import { type URLMatch, headersObjectToArray, isRegExp, isString, urlMatchesEqual, mkdirIfNeeded } from '../utils';
import type * as api from '../../types/types';
import type * as structs from '../../types/structs';
import { CDPSession } from './cdpSession';
@ -45,7 +44,6 @@ import { Dialog } from './dialog';
import { WebError } from './webError';
import { TargetClosedError, parseError } from './errors';
import { Clock } from './clock';
import type { MockingProxy } from './mockingProxy';
export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel> implements api.BrowserContext {
_pages = new Set<Page>();
@ -70,7 +68,6 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
_closeWasCalled = false;
private _closeReason: string | undefined;
private _harRouters: HarRouter[] = [];
_mockingProxy?: MockingProxy;
static from(context: channels.BrowserContextChannel): BrowserContext {
return (context as any)._object;
@ -169,7 +166,6 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
this.emit(Events.BrowserContext.Page, page);
if (page._opener && !page._opener.isClosed())
page._opener.emit(Events.Page.Popup, page);
this._mockingProxy?.instrumentPage(page);
}
_onRequest(request: network.Request, page: Page | null) {
@ -241,12 +237,6 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
await bindingCall.call(func);
}
async _subscribeToMockingProxy(mockingProxy: MockingProxy) {
if (this._mockingProxy)
throw new Error('Multiple mocking proxies are not supported');
this._mockingProxy = mockingProxy;
}
setDefaultNavigationTimeout(timeout: number | undefined) {
this._timeoutSettings.setDefaultNavigationTimeout(timeout);
this._wrapApiCall(async () => {
@ -530,7 +520,7 @@ function prepareRecordHarOptions(options: BrowserContextOptions['recordHar']): c
};
}
export async function prepareBrowserContextParams(options: BrowserContextOptions): Promise<channels.BrowserNewContextParams> {
export async function prepareBrowserContextParams(options: BrowserContextOptions, type?: BrowserType): Promise<channels.BrowserNewContextParams> {
if (options.videoSize && !options.videosPath)
throw new Error(`"videoSize" option requires "videosPath" to be specified`);
if (options.extraHTTPHeaders)
@ -548,6 +538,7 @@ export async function prepareBrowserContextParams(options: BrowserContextOptions
forcedColors: options.forcedColors === null ? 'no-override' : options.forcedColors,
acceptDownloads: toAcceptDownloadsProtocol(options.acceptDownloads),
clientCertificates: await toClientCertificatesProtocol(options.clientCertificates),
mockingProxyBaseURL: type?._playwright._mockingProxy?.baseURL(),
};
if (!contextParams.recordVideo && options.videosPath) {
contextParams.recordVideo = {

View file

@ -27,7 +27,6 @@ import { assert, headersObjectToArray, monotonicTime } from '../utils';
import type * as api from '../../types/types';
import { raceAgainstDeadline } from '../utils/timeoutRunner';
import type { Playwright } from './playwright';
import type { Page } from './page';
export interface BrowserServerLauncher {
launchServer(options?: LaunchServerOptions): Promise<api.BrowserServer>;
@ -96,7 +95,7 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel> imple
const logger = options.logger || this._defaultLaunchOptions?.logger;
assert(!(options as any).port, 'Cannot specify a port without launching as a server.');
options = { ...this._defaultLaunchOptions, ...this._defaultContextOptions, ...options };
const contextParams = await prepareBrowserContextParams(options);
const contextParams = await prepareBrowserContextParams(options, this);
const persistentParams: channels.BrowserTypeLaunchPersistentContextParams = {
...contextParams,
ignoreDefaultArgs: Array.isArray(options.ignoreDefaultArgs) ? options.ignoreDefaultArgs : undefined,
@ -243,13 +242,6 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel> imple
if (this._defaultContextNavigationTimeout !== undefined)
context.setDefaultNavigationTimeout(this._defaultContextNavigationTimeout);
if (this._playwright._mockingProxy) {
context.on(Events.BrowserContext.Page, (page: Page) => {
// TODO: funnel through protocol, so these headers are known to the server browsercontext and can be applied earlier
page.setExtraHTTPHeaders(this._playwright._mockingProxy!.instrumentationHeaders(page));
});
}
await this._instrumentation.runAfterCreateBrowserContext(context);
}

View file

@ -69,7 +69,7 @@ export class MockingProxy extends ChannelOwner<channels.MockingProxyChannel> {
}
findPage(correlation: string): Page | undefined {
const guid = `Page@${correlation}`;
const guid = `page@${correlation}`;
// TODO: move this as list onto Playwright directly
for (const browserType of [this._playwright.chromium, this._playwright.firefox, this._playwright.webkit]) {
for (const context of browserType._contexts) {
@ -81,10 +81,7 @@ export class MockingProxy extends ChannelOwner<channels.MockingProxyChannel> {
}
}
instrumentationHeaders(page: Page) {
const correlation = page._guid.substring('Page@'.length);
return {
'x-playwright-proxy': `${this._initializer.baseURL}/pw_meta:${correlation}/`,
};
baseURL() {
return this._initializer.baseURL;
}
}

View file

@ -775,6 +775,7 @@ scheme.BrowserNewContextParams = tObject({
cookies: tOptional(tArray(tType('SetNetworkCookie'))),
origins: tOptional(tArray(tType('OriginStorage'))),
})),
mockingProxyBaseURL: tOptional(tString),
});
scheme.BrowserNewContextResult = tObject({
context: tChannel(['BrowserContext']),

View file

@ -330,7 +330,7 @@ export class FFPage implements PageDelegate {
}
async updateExtraHTTPHeaders(): Promise<void> {
await this._session.send('Network.setExtraHTTPHeaders', { headers: this._page.extraHTTPHeaders() || [] });
await this._session.send('Network.setExtraHTTPHeaders', { headers: this._page.extraHTTPHeaders() });
}
async updateEmulatedViewportSize(): Promise<void> {

View file

@ -654,7 +654,7 @@ export class Frame extends SdkObject {
private async _gotoAction(progress: Progress, url: string, options: types.GotoOptions): Promise<network.Response | null> {
const waitUntil = verifyLifecycle('waitUntil', options.waitUntil === undefined ? 'load' : options.waitUntil);
progress.log(`navigating to "${url}", waiting until "${waitUntil}"`);
const headers = this._page.extraHTTPHeaders() || [];
const headers = this._page.extraHTTPHeaders();
const refererHeader = headers.find(h => h.name.toLowerCase() === 'referer');
let referer = refererHeader ? refererHeader.value : undefined;
if (options.referer !== undefined) {

View file

@ -365,8 +365,20 @@ export class Page extends SdkObject {
return this._delegate.updateExtraHTTPHeaders();
}
extraHTTPHeaders(): types.HeadersArray | undefined {
return this._extraHTTPHeaders;
extraHTTPHeaders(): types.HeadersArray {
return this.instrumentationHeaders().concat(this._extraHTTPHeaders ?? []);
}
instrumentationHeaders() {
const headers: channels.NameValue[] = [];
const mockingProxyBaseURL = this.context()._options.mockingProxyBaseURL;
if (mockingProxyBaseURL) {
const correlation = this.guid.substring('Page@'.length);
headers.push({ name: 'x-playwright-proxy', value: encodeURIComponent(mockingProxyBaseURL + `pw_meta:${correlation}/`) });
}
return headers;
}
async _onBindingCalled(payload: string, context: dom.FrameExecutionContext) {

View file

@ -57,7 +57,7 @@ type WorkerFixtures = PlaywrightWorkerArgs & PlaywrightWorkerOptions & {
_optionContextReuseMode: ContextReuseMode,
_optionConnectOptions: PlaywrightWorkerOptions['connectOptions'],
_reuseContext: boolean,
_mockingProxy?: MockingProxy,
_mockingProxy?: void,
};
const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
@ -124,12 +124,11 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
}, true);
}, { scope: 'worker', timeout: 0 }],
_mockingProxy: [async ({ mockingProxy: mockingProxyOption, playwright }, use) => {
if (mockingProxyOption !== 'inject-via-header')
return await use(undefined);
const mockingProxy = await (playwright as PlaywrightImpl)._startMockingProxy();
await use(mockingProxy);
}, { scope: 'worker', box: true }],
_mockingProxy: [async ({ mockingProxy, playwright }, use) => {
if (mockingProxy === 'inject-via-header')
await (playwright as PlaywrightImpl)._startMockingProxy();
await use();
}, { scope: 'worker', box: true, auto: true }],
acceptDownloads: [({ contextOptions }, use) => use(contextOptions.acceptDownloads ?? true), { option: true }],
bypassCSP: [({ contextOptions }, use) => use(contextOptions.bypassCSP ?? false), { option: true }],
@ -259,7 +258,7 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
}
}, { auto: 'all-hooks-included', title: 'context configuration', box: true } as any],
_setupArtifacts: [async ({ playwright, screenshot, _mockingProxy }, use, testInfo) => {
_setupArtifacts: [async ({ playwright, screenshot }, use, testInfo) => {
// This fixture has a separate zero-timeout slot to ensure that artifact collection
// happens even after some fixtures or hooks time out.
// Now that default test timeout is known, we can replace zero with an actual value.
@ -313,8 +312,6 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
currentTestInfo()?._setDebugMode();
},
runAfterCreateBrowserContext: async (context: BrowserContextImpl) => {
if (_mockingProxy)
await context._subscribeToMockingProxy(_mockingProxy);
await artifactsRecorder?.didCreateBrowserContext(context);
const testInfo = currentTestInfo();
if (testInfo)

View file

@ -1369,6 +1369,7 @@ export type BrowserNewContextParams = {
cookies?: SetNetworkCookie[],
origins?: OriginStorage[],
},
mockingProxyBaseURL?: string,
};
export type BrowserNewContextOptions = {
noDefaultViewport?: boolean,
@ -1435,6 +1436,7 @@ export type BrowserNewContextOptions = {
cookies?: SetNetworkCookie[],
origins?: OriginStorage[],
},
mockingProxyBaseURL?: string,
};
export type BrowserNewContextResult = {
context: BrowserContextChannel,

View file

@ -1038,6 +1038,7 @@ Browser:
origins:
type: array?
items: OriginStorage
mockingProxyBaseURL: string?
returns:
context: BrowserContext