Skip to content

Commit

Permalink
feat(fetch): fulfill without passing fetch response body client<->ser…
Browse files Browse the repository at this point in the history
…ver (#8789)
  • Loading branch information
yury-s committed Sep 8, 2021
1 parent 5a305a9 commit b11b274
Show file tree
Hide file tree
Showing 8 changed files with 65 additions and 9 deletions.
4 changes: 3 additions & 1 deletion src/client/browserContext.ts
Expand Up @@ -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) => {
const postDataBuffer = isString(options.postData) ? Buffer.from(options.postData, 'utf8') : options.postData;
const result = await channel.fetch({
Expand Down Expand Up @@ -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> {
if (options.videoSize && !options.videosPath)
throw new Error(`"videoSize" option requires "videosPath" to be specified`);
Expand Down
18 changes: 13 additions & 5 deletions src/client/network.ts
Expand Up @@ -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) => {
let useInterceptedResponseBody;
let fetchResponseUid;
let { status: statusOption, headers: headersOption, body: bodyOption } = options;
if (options.response) {
statusOption ||= options.response.status();
headersOption ||= options.response.headers();
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;
else
bodyOption = await options.response.body();
Expand Down Expand Up @@ -358,7 +361,8 @@ export class Route extends ChannelOwner<channels.RouteChannel, channels.RouteIni
headers: headersObjectToArray(headers),
body,
isBase64,
useInterceptedResponseBody
useInterceptedResponseBody,
fetchResponseUid
});
});
}
Expand Down Expand Up @@ -553,7 +557,7 @@ export class FetchResponse {

async body(): Promise<Buffer> {
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)
throw new Error('Response has been disposed');
return Buffer.from(result.binary!, 'base64');
Expand All @@ -572,9 +576,13 @@ export class FetchResponse {

async dispose(): Promise<void> {
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 {
Expand Down
7 changes: 6 additions & 1 deletion src/client/page.ts
Expand Up @@ -19,9 +19,10 @@ import { Events } from './events';
import { assert } from '../utils/utils';
import { TimeoutSettings } from '../utils/timeoutSettings';
import * as channels from '../protocol/channels';
import * as network from './network';
import { parseError, serializeError } from '../protocol/serializers';
import { Accessibility } from './accessibility';
import { BrowserContext } from './browserContext';
import { BrowserContext, FetchOptions } from './browserContext';
import { ChannelOwner } from './channelOwner';
import { ConsoleMessage } from './consoleMessage';
import { Dialog } from './dialog';
Expand Down Expand Up @@ -437,6 +438,10 @@ export class Page extends ChannelOwner<channels.PageChannel, channels.PageInitia
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) {
return this._wrapApiCall(async (channel: channels.PageChannel) => {
const source = await evaluationScript(script, arg);
Expand Down
2 changes: 2 additions & 0 deletions src/protocol/channels.ts
Expand Up @@ -2680,13 +2680,15 @@ export type RouteFulfillParams = {
body?: string,
isBase64?: boolean,
useInterceptedResponseBody?: boolean,
fetchResponseUid?: string,
};
export type RouteFulfillOptions = {
status?: number,
headers?: NameValue[],
body?: string,
isBase64?: boolean,
useInterceptedResponseBody?: boolean,
fetchResponseUid?: string,
};
export type RouteFulfillResult = void;
export type RouteResponseBodyParams = {};
Expand Down
1 change: 1 addition & 0 deletions src/protocol/protocol.yml
Expand Up @@ -2191,6 +2191,7 @@ Route:
body: string?
isBase64: boolean?
useInterceptedResponseBody: boolean?
fetchResponseUid: string?

responseBody:
returns:
Expand Down
1 change: 1 addition & 0 deletions src/protocol/validator.ts
Expand Up @@ -1049,6 +1049,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
body: tOptional(tString),
isBase64: tOptional(tBoolean),
useInterceptedResponseBody: tOptional(tBoolean),
fetchResponseUid: tOptional(tString),
});
scheme.RouteResponseBodyParams = tOptional(tObject({}));
scheme.ResourceTiming = tObject({
Expand Down
10 changes: 8 additions & 2 deletions src/server/network.ts
Expand Up @@ -219,13 +219,19 @@ export class Route extends SdkObject {
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!');
this._handled = true;
let body = overrides.body;
let isBase64 = overrides.isBase64 || false;
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');
isBase64 = false;
} else {
Expand Down
31 changes: 31 additions & 0 deletions tests/page/page-request-fulfill.spec.ts
Expand Up @@ -193,3 +193,34 @@ it('should include the origin header', async ({page, server, isAndroid}) => {
expect(text).toBe('done');
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'});
});

0 comments on commit b11b274

Please sign in to comment.