respect requestcontext options

This commit is contained in:
Simon Knott 2025-02-03 15:07:27 +01:00
parent 36855598be
commit b0cce73542
No known key found for this signature in database
GPG key ID: 8CEDC00028084AEC
6 changed files with 190 additions and 65 deletions

View file

@ -45,7 +45,7 @@ export type FetchOptions = {
maxRetries?: number,
};
type NewContextOptions = Omit<channels.PlaywrightNewRequestOptions, 'extraHTTPHeaders' | 'clientCertificates' | 'storageState' | 'tracesDir'> & {
export type NewContextOptions = Omit<channels.PlaywrightNewRequestOptions, 'extraHTTPHeaders' | 'clientCertificates' | 'storageState' | 'tracesDir'> & {
extraHTTPHeaders?: Headers,
storageState?: string | StorageState,
clientCertificates?: ClientCertificate[];
@ -65,13 +65,17 @@ export class APIRequest implements api.APIRequest {
}
async newContext(options: NewContextOptions = {}): Promise<APIRequestContext> {
return this._newContext(options, this._playwright._channel);
}
async _newContext(options: NewContextOptions = {}, channel: channels.PlaywrightChannel | channels.LocalUtilsChannel): Promise<APIRequestContext> {
options = { ...this._defaultContextOptions, ...options };
const storageState = typeof options.storageState === 'string' ?
JSON.parse(await fs.promises.readFile(options.storageState, 'utf8')) :
options.storageState;
// We do not expose tracesDir in the API, so do not allow options to accidentally override it.
const tracesDir = this._defaultContextOptions?.tracesDir;
const context = APIRequestContext.from((await this._playwright._channel.newRequest({
const context = APIRequestContext.from((await channel.newRequest({
...options,
extraHTTPHeaders: options.extraHTTPHeaders ? headersObjectToArray(options.extraHTTPHeaders) : undefined,
storageState,

View file

@ -20,7 +20,7 @@ import { Android } from './android';
import { BrowserType } from './browserType';
import { ChannelOwner } from './channelOwner';
import { Electron } from './electron';
import { APIRequest } from './fetch';
import { APIRequest, type NewContextOptions } from './fetch';
import { Selectors, SelectorsOwner } from './selectors';
export class Playwright extends ChannelOwner<channels.PlaywrightChannel> {
@ -74,8 +74,9 @@ export class Playwright extends ChannelOwner<channels.PlaywrightChannel> {
return (channel as any)._object;
}
async _startMockingProxy() {
const { mockingProxy } = await this._connection.localUtils()._channel.newMockingProxy({});
async _startMockingProxy(requestContextOptions: NewContextOptions) {
const requestContext = await this.request._newContext(requestContextOptions, this._connection.localUtils()._channel);
const { mockingProxy } = await this._connection.localUtils()._channel.newMockingProxy({ requestContext: requestContext._channel });
return (mockingProxy as any)._object;
}
}

View file

@ -336,7 +336,43 @@ scheme.LocalUtilsTraceDiscardedParams = tObject({
stacksId: tString,
});
scheme.LocalUtilsTraceDiscardedResult = tOptional(tObject({}));
scheme.LocalUtilsNewMockingProxyParams = tOptional(tObject({}));
scheme.LocalUtilsNewRequestParams = tObject({
baseURL: tOptional(tString),
userAgent: tOptional(tString),
ignoreHTTPSErrors: tOptional(tBoolean),
extraHTTPHeaders: tOptional(tArray(tType('NameValue'))),
clientCertificates: tOptional(tArray(tObject({
origin: tString,
cert: tOptional(tBinary),
key: tOptional(tBinary),
passphrase: tOptional(tString),
pfx: tOptional(tBinary),
}))),
httpCredentials: tOptional(tObject({
username: tString,
password: tString,
origin: tOptional(tString),
send: tOptional(tEnum(['always', 'unauthorized'])),
})),
proxy: tOptional(tObject({
server: tString,
bypass: tOptional(tString),
username: tOptional(tString),
password: tOptional(tString),
})),
timeout: tOptional(tNumber),
storageState: tOptional(tObject({
cookies: tOptional(tArray(tType('NetworkCookie'))),
origins: tOptional(tArray(tType('OriginStorage'))),
})),
tracesDir: tOptional(tString),
});
scheme.LocalUtilsNewRequestResult = tObject({
request: tChannel(['APIRequestContext']),
});
scheme.LocalUtilsNewMockingProxyParams = tObject({
requestContext: tChannel(['APIRequestContext']),
});
scheme.LocalUtilsNewMockingProxyResult = tObject({
mockingProxy: tChannel(['MockingProxy']),
});

View file

@ -41,10 +41,10 @@ import type { Playwright } from '../playwright';
import { SdkObject } from '../../server/instrumentation';
import { serializeClientSideCallMetadata } from '../../utils';
import { deviceDescriptors as descriptors } from '../deviceDescriptors';
import type { APIRequestContext } from '../fetch';
import { GlobalAPIRequestContext } from '../fetch';
import { MockingProxy } from '../mockingProxy';
import { MockingProxyDispatcher } from './mockingProxyDispatcher';
import { APIRequestContextDispatcher } from './networkDispatchers';
import { GlobalAPIRequestContext } from '../fetch';
export class LocalUtilsDispatcher extends Dispatcher<SdkObject, channels.LocalUtilsChannel, RootDispatcher> implements channels.LocalUtilsChannel {
_type_LocalUtils: boolean;
@ -55,21 +55,16 @@ export class LocalUtilsDispatcher extends Dispatcher<SdkObject, channels.LocalUt
tmpDir: string | undefined,
callStacks: channels.ClientSideCallMetadata[]
}>();
private _requestContext: APIRequestContext;
private _playwright: Playwright;
constructor(scope: RootDispatcher, playwright: Playwright) {
const localUtils = new SdkObject(playwright, 'localUtils', 'localUtils');
const deviceDescriptors = Object.entries(descriptors)
.map(([name, descriptor]) => ({ name, descriptor }));
const requestContext = new GlobalAPIRequestContext(
playwright,
{} // TODO: this should probably respect _combinedContextOptions from test runner
);
super(scope, localUtils, 'LocalUtils', {
deviceDescriptors,
});
this._requestContext = requestContext;
this._playwright = playwright;
this._type_LocalUtils = true;
}
@ -285,8 +280,14 @@ export class LocalUtilsDispatcher extends Dispatcher<SdkObject, channels.LocalUt
this._stackSessions.delete(stacksId!);
}
async newRequest(params: channels.LocalUtilsNewRequestParams, metadata?: CallMetadata): Promise<channels.LocalUtilsNewRequestResult> {
const requestContext = new GlobalAPIRequestContext(this._playwright, params);
return { request: APIRequestContextDispatcher.from(this.parentScope(), requestContext) };
}
async newMockingProxy(params: channels.LocalUtilsNewMockingProxyParams, metadata?: CallMetadata): Promise<channels.LocalUtilsNewMockingProxyResult> {
const mockingProxy = new MockingProxy(this._object, this._requestContext);
const requestContext = (params.requestContext as APIRequestContextDispatcher)._object;
const mockingProxy = new MockingProxy(this._object, requestContext);
await mockingProxy.start();
return { mockingProxy: MockingProxyDispatcher.from(this.parentScope(), mockingProxy) };
}

View file

@ -466,7 +466,8 @@ export interface LocalUtilsChannel extends LocalUtilsEventTarget, Channel {
tracingStarted(params: LocalUtilsTracingStartedParams, metadata?: CallMetadata): Promise<LocalUtilsTracingStartedResult>;
addStackToTracingNoReply(params: LocalUtilsAddStackToTracingNoReplyParams, metadata?: CallMetadata): Promise<LocalUtilsAddStackToTracingNoReplyResult>;
traceDiscarded(params: LocalUtilsTraceDiscardedParams, metadata?: CallMetadata): Promise<LocalUtilsTraceDiscardedResult>;
newMockingProxy(params?: LocalUtilsNewMockingProxyParams, metadata?: CallMetadata): Promise<LocalUtilsNewMockingProxyResult>;
newRequest(params: LocalUtilsNewRequestParams, metadata?: CallMetadata): Promise<LocalUtilsNewRequestResult>;
newMockingProxy(params: LocalUtilsNewMockingProxyParams, metadata?: CallMetadata): Promise<LocalUtilsNewMockingProxyResult>;
}
export type LocalUtilsZipParams = {
zipFile: string,
@ -566,8 +567,77 @@ export type LocalUtilsTraceDiscardedOptions = {
};
export type LocalUtilsTraceDiscardedResult = void;
export type LocalUtilsNewMockingProxyParams = {};
export type LocalUtilsNewMockingProxyOptions = {};
export type LocalUtilsNewRequestParams = {
baseURL?: string,
userAgent?: string,
ignoreHTTPSErrors?: boolean,
extraHTTPHeaders?: NameValue[],
clientCertificates?: {
origin: string,
cert?: Binary,
key?: Binary,
passphrase?: string,
pfx?: Binary,
}[],
httpCredentials?: {
username: string,
password: string,
origin?: string,
send?: 'always' | 'unauthorized',
},
proxy?: {
server: string,
bypass?: string,
username?: string,
password?: string,
},
timeout?: number,
storageState?: {
cookies?: NetworkCookie[],
origins?: OriginStorage[],
},
tracesDir?: string,
};
export type LocalUtilsNewRequestOptions = {
baseURL?: string,
userAgent?: string,
ignoreHTTPSErrors?: boolean,
extraHTTPHeaders?: NameValue[],
clientCertificates?: {
origin: string,
cert?: Binary,
key?: Binary,
passphrase?: string,
pfx?: Binary,
}[],
httpCredentials?: {
username: string,
password: string,
origin?: string,
send?: 'always' | 'unauthorized',
},
proxy?: {
server: string,
bypass?: string,
username?: string,
password?: string,
},
timeout?: number,
storageState?: {
cookies?: NetworkCookie[],
origins?: OriginStorage[],
},
tracesDir?: string,
};
export type LocalUtilsNewRequestResult = {
request: APIRequestContextChannel,
};
export type LocalUtilsNewMockingProxyParams = {
requestContext: APIRequestContextChannel,
};
export type LocalUtilsNewMockingProxyOptions = {
};
export type LocalUtilsNewMockingProxyResult = {
mockingProxy: MockingProxyChannel,
};

View file

@ -526,6 +526,56 @@ ContextOptions:
- allow
- block
NewRequestParameters:
type: mixin
properties:
baseURL: string?
userAgent: string?
ignoreHTTPSErrors: boolean?
extraHTTPHeaders:
type: array?
items: NameValue
clientCertificates:
type: array?
items:
type: object
properties:
origin: string
cert: binary?
key: binary?
passphrase: string?
pfx: binary?
httpCredentials:
type: object?
properties:
username: string
password: string
origin: string?
send:
type: enum?
literals:
- always
- unauthorized
proxy:
type: object?
properties:
server: string
bypass: string?
username: string?
password: string?
timeout: number?
storageState:
type: object?
properties:
cookies:
type: array?
items: NetworkCookie
origins:
type: array?
items: OriginStorage
tracesDir: string?
EventTarget:
type: interface
@ -669,7 +719,15 @@ LocalUtils:
parameters:
stacksId: string
newRequest:
parameters:
$mixin: NewRequestParameters
returns:
request: APIRequestContext
newMockingProxy:
parameters:
requestContext: APIRequestContext
returns:
mockingProxy: MockingProxy
@ -748,52 +806,7 @@ Playwright:
commands:
newRequest:
parameters:
baseURL: string?
userAgent: string?
ignoreHTTPSErrors: boolean?
extraHTTPHeaders:
type: array?
items: NameValue
clientCertificates:
type: array?
items:
type: object
properties:
origin: string
cert: binary?
key: binary?
passphrase: string?
pfx: binary?
httpCredentials:
type: object?
properties:
username: string
password: string
origin: string?
send:
type: enum?
literals:
- always
- unauthorized
proxy:
type: object?
properties:
server: string
bypass: string?
username: string?
password: string?
timeout: number?
storageState:
type: object?
properties:
cookies:
type: array?
items: NetworkCookie
origins:
type: array?
items: OriginStorage
tracesDir: string?
$mixin: NewRequestParameters
returns:
request: APIRequestContext