mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
feat(fetch): fulfill without passing fetch response body client<->server (#8789)
This commit is contained in:
parent
5a305a9c2e
commit
b11b274b0d
@ -216,7 +216,7 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel,
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async _fetch(url: string, options: { url?: string, method?: string, headers?: Headers, postData?: string | Buffer, timeout?: number } = {}): Promise<network.FetchResponse> {
|
async _fetch(url: string, options: FetchOptions = {}): Promise<network.FetchResponse> {
|
||||||
return this._wrapApiCall(async (channel: channels.BrowserContextChannel) => {
|
return this._wrapApiCall(async (channel: channels.BrowserContextChannel) => {
|
||||||
const postDataBuffer = isString(options.postData) ? Buffer.from(options.postData, 'utf8') : options.postData;
|
const postDataBuffer = isString(options.postData) ? Buffer.from(options.postData, 'utf8') : options.postData;
|
||||||
const result = await channel.fetch({
|
const result = await channel.fetch({
|
||||||
@ -383,6 +383,8 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type FetchOptions = { url?: string, method?: string, headers?: Headers, postData?: string | Buffer, timeout?: number };
|
||||||
|
|
||||||
export async function prepareBrowserContextParams(options: BrowserContextOptions): Promise<channels.BrowserNewContextParams> {
|
export async function prepareBrowserContextParams(options: BrowserContextOptions): Promise<channels.BrowserNewContextParams> {
|
||||||
if (options.videoSize && !options.videosPath)
|
if (options.videoSize && !options.videosPath)
|
||||||
throw new Error(`"videoSize" option requires "videosPath" to be specified`);
|
throw new Error(`"videoSize" option requires "videosPath" to be specified`);
|
||||||
|
|||||||
@ -310,15 +310,18 @@ export class Route extends ChannelOwner<channels.RouteChannel, channels.RouteIni
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async fulfill(options: { response?: Response, status?: number, headers?: Headers, contentType?: string, body?: string | Buffer, path?: string } = {}) {
|
async fulfill(options: { response?: Response|FetchResponse, status?: number, headers?: Headers, contentType?: string, body?: string | Buffer, path?: string } = {}) {
|
||||||
return this._wrapApiCall(async (channel: channels.RouteChannel) => {
|
return this._wrapApiCall(async (channel: channels.RouteChannel) => {
|
||||||
let useInterceptedResponseBody;
|
let useInterceptedResponseBody;
|
||||||
|
let fetchResponseUid;
|
||||||
let { status: statusOption, headers: headersOption, body: bodyOption } = options;
|
let { status: statusOption, headers: headersOption, body: bodyOption } = options;
|
||||||
if (options.response) {
|
if (options.response) {
|
||||||
statusOption ||= options.response.status();
|
statusOption ||= options.response.status();
|
||||||
headersOption ||= options.response.headers();
|
headersOption ||= options.response.headers();
|
||||||
if (options.body === undefined && options.path === undefined) {
|
if (options.body === undefined && options.path === undefined) {
|
||||||
if (options.response === this._interceptedResponse)
|
if (options.response instanceof FetchResponse)
|
||||||
|
fetchResponseUid = (options.response as FetchResponse)._fetchUid();
|
||||||
|
else if (options.response === this._interceptedResponse)
|
||||||
useInterceptedResponseBody = true;
|
useInterceptedResponseBody = true;
|
||||||
else
|
else
|
||||||
bodyOption = await options.response.body();
|
bodyOption = await options.response.body();
|
||||||
@ -358,7 +361,8 @@ export class Route extends ChannelOwner<channels.RouteChannel, channels.RouteIni
|
|||||||
headers: headersObjectToArray(headers),
|
headers: headersObjectToArray(headers),
|
||||||
body,
|
body,
|
||||||
isBase64,
|
isBase64,
|
||||||
useInterceptedResponseBody
|
useInterceptedResponseBody,
|
||||||
|
fetchResponseUid
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -553,7 +557,7 @@ export class FetchResponse {
|
|||||||
|
|
||||||
async body(): Promise<Buffer> {
|
async body(): Promise<Buffer> {
|
||||||
return this._context._wrapApiCall(async (channel: channels.BrowserContextChannel) => {
|
return this._context._wrapApiCall(async (channel: channels.BrowserContextChannel) => {
|
||||||
const result = await channel.fetchResponseBody({ fetchUid: this._initializer.fetchUid });
|
const result = await channel.fetchResponseBody({ fetchUid: this._fetchUid() });
|
||||||
if (!result.binary)
|
if (!result.binary)
|
||||||
throw new Error('Response has been disposed');
|
throw new Error('Response has been disposed');
|
||||||
return Buffer.from(result.binary!, 'base64');
|
return Buffer.from(result.binary!, 'base64');
|
||||||
@ -572,9 +576,13 @@ export class FetchResponse {
|
|||||||
|
|
||||||
async dispose(): Promise<void> {
|
async dispose(): Promise<void> {
|
||||||
return this._context._wrapApiCall(async (channel: channels.BrowserContextChannel) => {
|
return this._context._wrapApiCall(async (channel: channels.BrowserContextChannel) => {
|
||||||
await channel.disposeFetchResponse({ fetchUid: this._initializer.fetchUid });
|
await channel.disposeFetchResponse({ fetchUid: this._fetchUid() });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_fetchUid(): string {
|
||||||
|
return this._initializer.fetchUid;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class WebSocket extends ChannelOwner<channels.WebSocketChannel, channels.WebSocketInitializer> implements api.WebSocket {
|
export class WebSocket extends ChannelOwner<channels.WebSocketChannel, channels.WebSocketInitializer> implements api.WebSocket {
|
||||||
|
|||||||
@ -19,9 +19,10 @@ import { Events } from './events';
|
|||||||
import { assert } from '../utils/utils';
|
import { assert } from '../utils/utils';
|
||||||
import { TimeoutSettings } from '../utils/timeoutSettings';
|
import { TimeoutSettings } from '../utils/timeoutSettings';
|
||||||
import * as channels from '../protocol/channels';
|
import * as channels from '../protocol/channels';
|
||||||
|
import * as network from './network';
|
||||||
import { parseError, serializeError } from '../protocol/serializers';
|
import { parseError, serializeError } from '../protocol/serializers';
|
||||||
import { Accessibility } from './accessibility';
|
import { Accessibility } from './accessibility';
|
||||||
import { BrowserContext } from './browserContext';
|
import { BrowserContext, FetchOptions } from './browserContext';
|
||||||
import { ChannelOwner } from './channelOwner';
|
import { ChannelOwner } from './channelOwner';
|
||||||
import { ConsoleMessage } from './consoleMessage';
|
import { ConsoleMessage } from './consoleMessage';
|
||||||
import { Dialog } from './dialog';
|
import { Dialog } from './dialog';
|
||||||
@ -437,6 +438,10 @@ export class Page extends ChannelOwner<channels.PageChannel, channels.PageInitia
|
|||||||
return this._mainFrame.evaluate(pageFunction, arg);
|
return this._mainFrame.evaluate(pageFunction, arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async _fetch(url: string, options: FetchOptions = {}): Promise<network.FetchResponse> {
|
||||||
|
return await this._browserContext._fetch(url, options);
|
||||||
|
}
|
||||||
|
|
||||||
async addInitScript(script: Function | string | { path?: string, content?: string }, arg?: any) {
|
async addInitScript(script: Function | string | { path?: string, content?: string }, arg?: any) {
|
||||||
return this._wrapApiCall(async (channel: channels.PageChannel) => {
|
return this._wrapApiCall(async (channel: channels.PageChannel) => {
|
||||||
const source = await evaluationScript(script, arg);
|
const source = await evaluationScript(script, arg);
|
||||||
|
|||||||
@ -2680,6 +2680,7 @@ export type RouteFulfillParams = {
|
|||||||
body?: string,
|
body?: string,
|
||||||
isBase64?: boolean,
|
isBase64?: boolean,
|
||||||
useInterceptedResponseBody?: boolean,
|
useInterceptedResponseBody?: boolean,
|
||||||
|
fetchResponseUid?: string,
|
||||||
};
|
};
|
||||||
export type RouteFulfillOptions = {
|
export type RouteFulfillOptions = {
|
||||||
status?: number,
|
status?: number,
|
||||||
@ -2687,6 +2688,7 @@ export type RouteFulfillOptions = {
|
|||||||
body?: string,
|
body?: string,
|
||||||
isBase64?: boolean,
|
isBase64?: boolean,
|
||||||
useInterceptedResponseBody?: boolean,
|
useInterceptedResponseBody?: boolean,
|
||||||
|
fetchResponseUid?: string,
|
||||||
};
|
};
|
||||||
export type RouteFulfillResult = void;
|
export type RouteFulfillResult = void;
|
||||||
export type RouteResponseBodyParams = {};
|
export type RouteResponseBodyParams = {};
|
||||||
|
|||||||
@ -2191,6 +2191,7 @@ Route:
|
|||||||
body: string?
|
body: string?
|
||||||
isBase64: boolean?
|
isBase64: boolean?
|
||||||
useInterceptedResponseBody: boolean?
|
useInterceptedResponseBody: boolean?
|
||||||
|
fetchResponseUid: string?
|
||||||
|
|
||||||
responseBody:
|
responseBody:
|
||||||
returns:
|
returns:
|
||||||
|
|||||||
@ -1049,6 +1049,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
|||||||
body: tOptional(tString),
|
body: tOptional(tString),
|
||||||
isBase64: tOptional(tBoolean),
|
isBase64: tOptional(tBoolean),
|
||||||
useInterceptedResponseBody: tOptional(tBoolean),
|
useInterceptedResponseBody: tOptional(tBoolean),
|
||||||
|
fetchResponseUid: tOptional(tString),
|
||||||
});
|
});
|
||||||
scheme.RouteResponseBodyParams = tOptional(tObject({}));
|
scheme.RouteResponseBodyParams = tOptional(tObject({}));
|
||||||
scheme.ResourceTiming = tObject({
|
scheme.ResourceTiming = tObject({
|
||||||
|
|||||||
@ -219,13 +219,19 @@ export class Route extends SdkObject {
|
|||||||
await this._delegate.abort(errorCode);
|
await this._delegate.abort(errorCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
async fulfill(overrides: { status?: number, headers?: types.HeadersArray, body?: string, isBase64?: boolean, useInterceptedResponseBody?: boolean }) {
|
async fulfill(overrides: { status?: number, headers?: types.HeadersArray, body?: string, isBase64?: boolean, useInterceptedResponseBody?: boolean, fetchResponseUid?: string }) {
|
||||||
assert(!this._handled, 'Route is already handled!');
|
assert(!this._handled, 'Route is already handled!');
|
||||||
this._handled = true;
|
this._handled = true;
|
||||||
let body = overrides.body;
|
let body = overrides.body;
|
||||||
let isBase64 = overrides.isBase64 || false;
|
let isBase64 = overrides.isBase64 || false;
|
||||||
if (body === undefined) {
|
if (body === undefined) {
|
||||||
if (this._response && overrides.useInterceptedResponseBody) {
|
if (overrides.fetchResponseUid) {
|
||||||
|
const context = this._request.frame()._page._browserContext;
|
||||||
|
const buffer = context.fetchResponses.get(overrides.fetchResponseUid);
|
||||||
|
assert(buffer, 'Fetch response has been disposed');
|
||||||
|
body = buffer.toString('utf8');
|
||||||
|
isBase64 = false;
|
||||||
|
} else if (this._response && overrides.useInterceptedResponseBody) {
|
||||||
body = (await this._delegate.responseBody()).toString('utf8');
|
body = (await this._delegate.responseBody()).toString('utf8');
|
||||||
isBase64 = false;
|
isBase64 = false;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -193,3 +193,34 @@ it('should include the origin header', async ({page, server, isAndroid}) => {
|
|||||||
expect(text).toBe('done');
|
expect(text).toBe('done');
|
||||||
expect(interceptedRequest.headers()['origin']).toEqual(server.PREFIX);
|
expect(interceptedRequest.headers()['origin']).toEqual(server.PREFIX);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should fulfill with fetch result', async ({page, server}) => {
|
||||||
|
await page.route('**/*', async route => {
|
||||||
|
// @ts-expect-error
|
||||||
|
const response = await page._fetch(server.PREFIX + '/simple.json');
|
||||||
|
// @ts-expect-error
|
||||||
|
route.fulfill({ response });
|
||||||
|
});
|
||||||
|
const response = await page.goto(server.EMPTY_PAGE);
|
||||||
|
expect(response.status()).toBe(200);
|
||||||
|
expect(await response.json()).toEqual({'foo': 'bar'});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fulfill with fetch result and overrides', async ({page, server}) => {
|
||||||
|
await page.route('**/*', async route => {
|
||||||
|
// @ts-expect-error
|
||||||
|
const response = await page._fetch(server.PREFIX + '/simple.json');
|
||||||
|
route.fulfill({
|
||||||
|
// @ts-expect-error
|
||||||
|
response,
|
||||||
|
status: 201,
|
||||||
|
headers: {
|
||||||
|
'foo': 'bar'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
const response = await page.goto(server.EMPTY_PAGE);
|
||||||
|
expect(response.status()).toBe(201);
|
||||||
|
expect((await response.allHeaders()).foo).toEqual('bar');
|
||||||
|
expect(await response.json()).toEqual({'foo': 'bar'});
|
||||||
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user