feat(route): rename method, add response option (#8386)

This commit is contained in:
Yury Semikhatsky 2021-08-24 11:07:54 -07:00 committed by GitHub
parent 20e4d9eee5
commit 59422a00f5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 115 additions and 36 deletions

View file

@ -181,6 +181,11 @@ page.route("**/xhr_endpoint", lambda route: route.fulfill(path="mock_data.json")
await page.RouteAsync("**/xhr_endpoint", route => route.FulfillAsync(new RouteFulfillOptions { Path = "mock_data.json" }));
```
### option: Route.fulfill._response
- `_response` <[Response]>
Intercepted response. Will be used to populate all response fields not explicitely overridden.
### option: Route.fulfill.status
- `status` <[int]>

View file

@ -245,6 +245,8 @@ type InterceptResponse = true;
type NotInterceptResponse = false;
export class Route extends ChannelOwner<channels.RouteChannel, channels.RouteInitializer> implements api.Route {
private _interceptedResponse: api.Response | undefined;
static from(route: channels.RouteChannel): Route {
return (route as any)._object;
}
@ -263,8 +265,21 @@ export class Route extends ChannelOwner<channels.RouteChannel, channels.RouteIni
});
}
async fulfill(options: { status?: number, headers?: Headers, contentType?: string, body?: string | Buffer, path?: string } = {}) {
async fulfill(options: { _response?: Response, status?: number, headers?: Headers, contentType?: string, body?: string | Buffer, path?: string } = {}) {
return this._wrapApiCall(async (channel: channels.RouteChannel) => {
let useInterceptedResponseBody;
let { status: statusOption, headers: headersOption, body: bodyOption } = options;
if (options._response) {
statusOption = statusOption || options._response.status();
headersOption = headersOption || options._response.headers();
if (options.body === undefined && options.path === undefined) {
if (options._response === this._interceptedResponse)
useInterceptedResponseBody = true;
else
bodyOption = await options._response.body();
}
}
let body = undefined;
let isBase64 = false;
let length = 0;
@ -273,19 +288,19 @@ export class Route extends ChannelOwner<channels.RouteChannel, channels.RouteIni
body = buffer.toString('base64');
isBase64 = true;
length = buffer.length;
} else if (isString(options.body)) {
body = options.body;
} else if (isString(bodyOption)) {
body = bodyOption;
isBase64 = false;
length = Buffer.byteLength(body);
} else if (options.body) {
body = options.body.toString('base64');
} else if (bodyOption) {
body = bodyOption.toString('base64');
isBase64 = true;
length = options.body.length;
length = bodyOption.length;
}
const headers: Headers = {};
for (const header of Object.keys(options.headers || {}))
headers[header.toLowerCase()] = String(options.headers![header]);
for (const header of Object.keys(headersOption || {}))
headers[header.toLowerCase()] = String(headersOption![header]);
if (options.contentType)
headers['content-type'] = String(options.contentType);
else if (options.path)
@ -294,16 +309,18 @@ export class Route extends ChannelOwner<channels.RouteChannel, channels.RouteIni
headers['content-length'] = String(length);
await channel.fulfill({
status: options.status || 200,
status: statusOption || 200,
headers: headersObjectToArray(headers),
body,
isBase64
isBase64,
useInterceptedResponseBody
});
});
}
async _intercept(options: { url?: string, method?: string, headers?: Headers, postData?: string | Buffer, interceptResponse?: boolean } = {}): Promise<api.Response> {
return await this._continue(options, true);
async _continueToResponse(options: { url?: string, method?: string, headers?: Headers, postData?: string | Buffer, interceptResponse?: boolean } = {}): Promise<api.Response> {
this._interceptedResponse = await this._continue(options, true);
return this._interceptedResponse;
}
async continue(options: { url?: string, method?: string, headers?: Headers, postData?: string | Buffer } = {}) {

View file

@ -2531,12 +2531,14 @@ export type RouteFulfillParams = {
headers?: NameValue[],
body?: string,
isBase64?: boolean,
useInterceptedResponseBody?: boolean,
};
export type RouteFulfillOptions = {
status?: number,
headers?: NameValue[],
body?: string,
isBase64?: boolean,
useInterceptedResponseBody?: boolean,
};
export type RouteFulfillResult = void;
export type RouteResponseBodyParams = {};

View file

@ -2134,6 +2134,7 @@ Route:
items: NameValue
body: string?
isBase64: boolean?
useInterceptedResponseBody: boolean?
responseBody:
returns:

View file

@ -1018,6 +1018,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
headers: tOptional(tArray(tType('NameValue'))),
body: tOptional(tString),
isBase64: tOptional(tBoolean),
useInterceptedResponseBody: tOptional(tBoolean),
});
scheme.RouteResponseBodyParams = tOptional(tObject({}));
scheme.ResourceTiming = tObject({

View file

@ -217,13 +217,13 @@ export class Route extends SdkObject {
await this._delegate.abort(errorCode);
}
async fulfill(overrides: { status?: number, headers?: types.HeadersArray, body?: string, isBase64?: boolean }) {
async fulfill(overrides: { status?: number, headers?: types.HeadersArray, body?: string, isBase64?: boolean, useInterceptedResponseBody?: boolean }) {
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) {
if (this._response && overrides.useInterceptedResponseBody) {
body = (await this._delegate.responseBody()).toString('utf8');
isBase64 = false;
} else {
@ -232,8 +232,8 @@ export class Route extends SdkObject {
}
}
await this._delegate.fulfill({
status: overrides.status || this._response?.status() || 200,
headers: overrides.headers || this._response?.headers() || [],
status: overrides.status || 200,
headers: overrides.headers || [],
body,
isBase64,
});

View file

@ -23,7 +23,7 @@ import { expect, test as it } from './pageTest';
it('should fulfill intercepted response', async ({page, server, browserName}) => {
await page.route('**/*', async route => {
// @ts-expect-error
await route._intercept({});
await route._continueToResponse({});
await route.fulfill({
status: 201,
headers: {
@ -44,8 +44,9 @@ it('should fulfill response with empty body', async ({page, server, browserName,
it.skip(browserName === 'chromium' && browserMajorVersion <= 91, 'Fails in Electron that uses old Chromium');
await page.route('**/*', async route => {
// @ts-expect-error
await route._intercept({});
const _response = await route._continueToResponse({});
await route.fulfill({
_response,
status: 201,
body: ''
});
@ -55,6 +56,53 @@ it('should fulfill response with empty body', async ({page, server, browserName,
expect(await response.text()).toBe('');
});
it('should override with defaults when intercepted response not provided', async ({page, server, browserName, browserMajorVersion}) => {
it.skip(browserName === 'chromium' && browserMajorVersion <= 91, 'Fails in Electron that uses old Chromium');
server.setRoute('/empty.html', (req, res) => {
res.setHeader('foo', 'bar');
res.end('my content');
});
await page.route('**/*', async route => {
// @ts-expect-error
await route._continueToResponse({});
await route.fulfill({
status: 201,
});
});
const response = await page.goto(server.EMPTY_PAGE);
expect(response.status()).toBe(201);
expect(await response.text()).toBe('');
if (browserName === 'webkit')
expect(response.headers()).toEqual({'content-type': 'text/plain'});
else
expect(response.headers()).toEqual({ });
});
it('should fulfill with any response', async ({page, server, browserName, browserMajorVersion, isLinux}) => {
it.skip(browserName === 'chromium' && browserMajorVersion <= 91, 'Fails in Electron that uses old Chromium');
it.fail(browserName === 'webkit' && isLinux, 'Network.responseReceived comes twice');
server.setRoute('/sample', (req, res) => {
res.setHeader('foo', 'bar');
res.end('Woo-hoo');
});
const page2 = await page.context().newPage();
const sampleResponse = await page2.goto(`${server.PREFIX}/sample`);
await page.route('**/*', async route => {
// @ts-expect-error
await route._continueToResponse({});
await route.fulfill({
_response: sampleResponse,
status: 201,
});
});
const response = await page.goto(server.EMPTY_PAGE);
expect(response.status()).toBe(201);
expect(await response.text()).toBe('Woo-hoo');
expect(response.headers()['foo']).toBe('bar');
});
it('should throw on continue after intercept', async ({page, server, browserName}) => {
let routeCallback;
const routePromise = new Promise<Route>(f => routeCallback = f);
@ -63,7 +111,7 @@ it('should throw on continue after intercept', async ({page, server, browserName
page.goto(server.EMPTY_PAGE).catch(e => {});
const route = await routePromise;
// @ts-expect-error
await route._intercept();
await route._continueToResponse();
try {
await route.continue();
fail('did not throw');
@ -76,8 +124,8 @@ it('should support fulfill after intercept', async ({page, server}) => {
const requestPromise = server.waitForRequest('/title.html');
await page.route('**', async route => {
// @ts-expect-error
await route._intercept();
await route.fulfill();
const _response = await route._continueToResponse();
await route.fulfill({ _response });
});
const response = await page.goto(server.PREFIX + '/title.html');
const request = await requestPromise;
@ -95,8 +143,8 @@ it('should intercept failures', async ({page, browserName, browserMajorVersion,
await page.route('**', async route => {
try {
// @ts-expect-error
await route._intercept();
await route.fulfill();
const _response = await route._continueToResponse();
await route.fulfill({ _response });
} catch (e) {
error = e;
}
@ -115,13 +163,13 @@ it('should support request overrides', async ({page, server, browserName, browse
const requestPromise = server.waitForRequest('/empty.html');
await page.route('**/foo', async route => {
// @ts-expect-error
await route._intercept({
const _response = await route._continueToResponse({
url: server.EMPTY_PAGE,
method: 'POST',
headers: {'foo': 'bar'},
postData: 'my data',
});
await route.fulfill();
await route.fulfill({ _response });
});
await page.goto(server.PREFIX + '/foo');
const request = await requestPromise;
@ -142,14 +190,14 @@ it('should give access to the intercepted response', async ({page, server}) => {
const route = await routePromise;
// @ts-expect-error
const response = await route._intercept();
const response = await route._continueToResponse();
expect(response.status()).toBe(200);
expect(response.ok()).toBeTruthy();
expect(response.url()).toBe(server.PREFIX + '/title.html');
expect(response.headers()['content-type']).toBe('text/html; charset=utf-8');
await Promise.all([route.fulfill(), evalPromise]);
await Promise.all([route.fulfill({ _response: response }), evalPromise]);
});
it('should give access to the intercepted response status text', async ({page, server, browserName}) => {
@ -167,12 +215,12 @@ it('should give access to the intercepted response status text', async ({page, s
const evalPromise = page.evaluate(url => fetch(url), server.PREFIX + '/title.html');
const route = await routePromise;
// @ts-expect-error
const response = await route._intercept();
const response = await route._continueToResponse();
expect(response.statusText()).toBe('You are awesome');
expect(response.url()).toBe(server.PREFIX + '/title.html');
await Promise.all([route.fulfill(), evalPromise]);
await Promise.all([route.fulfill({ _response: response }), evalPromise]);
});
it('should give access to the intercepted response body', async ({page, server}) => {
@ -186,17 +234,17 @@ it('should give access to the intercepted response body', async ({page, server})
const route = await routePromise;
// @ts-expect-error
const response = await route._intercept();
const response = await route._continueToResponse();
expect((await response.text())).toBe('{"foo": "bar"}\n');
await Promise.all([route.fulfill(), evalPromise]);
await Promise.all([route.fulfill({ _response: response }), evalPromise]);
});
it('should be abortable after interception', async ({page, server, browserName}) => {
await page.route(/\.css$/, async route => {
// @ts-expect-error
await route._intercept();
await route._continueToResponse();
await route.abort();
});
let failed = false;
@ -224,7 +272,7 @@ it('should fulfill after redirects', async ({page, server, browserName}) => {
await page.route('**/*', async route => {
++routeCalls;
// @ts-expect-error
await route._intercept({});
await route._continueToResponse({});
await route.fulfill({
status: 201,
headers: {
@ -266,8 +314,8 @@ it('should fulfill original response after redirects', async ({page, browserName
await page.route('**/*', async route => {
++routeCalls;
// @ts-expect-error
await route._intercept({});
await route.fulfill();
const _response = await route._continueToResponse({});
await route.fulfill({ _response });
});
const response = await page.goto(server.PREFIX + '/redirect/1.html');
expect(requestUrls).toEqual(expectedUrls);
@ -301,7 +349,7 @@ it('should abort after redirects', async ({page, browserName, server}) => {
await page.route('**/*', async route => {
++routeCalls;
// @ts-expect-error
await route._intercept({});
await route._continueToResponse({});
await route.abort('connectionreset');
});

5
types/types.d.ts vendored
View file

@ -11927,6 +11927,11 @@ export interface Route {
* @param options
*/
fulfill(options?: {
/**
* Intercepted response. Will be used to populate all response fields not explicitely overridden.
*/
_response?: Response;
/**
* Response body.
*/