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" })); 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 ### option: Route.fulfill.status
- `status` <[int]> - `status` <[int]>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -23,7 +23,7 @@ import { expect, test as it } from './pageTest';
it('should fulfill intercepted response', async ({page, server, browserName}) => { it('should fulfill intercepted response', async ({page, server, browserName}) => {
await page.route('**/*', async route => { await page.route('**/*', async route => {
// @ts-expect-error // @ts-expect-error
await route._intercept({}); await route._continueToResponse({});
await route.fulfill({ await route.fulfill({
status: 201, status: 201,
headers: { 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'); it.skip(browserName === 'chromium' && browserMajorVersion <= 91, 'Fails in Electron that uses old Chromium');
await page.route('**/*', async route => { await page.route('**/*', async route => {
// @ts-expect-error // @ts-expect-error
await route._intercept({}); const _response = await route._continueToResponse({});
await route.fulfill({ await route.fulfill({
_response,
status: 201, status: 201,
body: '' body: ''
}); });
@ -55,6 +56,53 @@ it('should fulfill response with empty body', async ({page, server, browserName,
expect(await response.text()).toBe(''); 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}) => { it('should throw on continue after intercept', async ({page, server, browserName}) => {
let routeCallback; let routeCallback;
const routePromise = new Promise<Route>(f => routeCallback = f); 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 => {}); page.goto(server.EMPTY_PAGE).catch(e => {});
const route = await routePromise; const route = await routePromise;
// @ts-expect-error // @ts-expect-error
await route._intercept(); await route._continueToResponse();
try { try {
await route.continue(); await route.continue();
fail('did not throw'); fail('did not throw');
@ -76,8 +124,8 @@ it('should support fulfill after intercept', async ({page, server}) => {
const requestPromise = server.waitForRequest('/title.html'); const requestPromise = server.waitForRequest('/title.html');
await page.route('**', async route => { await page.route('**', async route => {
// @ts-expect-error // @ts-expect-error
await route._intercept(); const _response = await route._continueToResponse();
await route.fulfill(); await route.fulfill({ _response });
}); });
const response = await page.goto(server.PREFIX + '/title.html'); const response = await page.goto(server.PREFIX + '/title.html');
const request = await requestPromise; const request = await requestPromise;
@ -95,8 +143,8 @@ it('should intercept failures', async ({page, browserName, browserMajorVersion,
await page.route('**', async route => { await page.route('**', async route => {
try { try {
// @ts-expect-error // @ts-expect-error
await route._intercept(); const _response = await route._continueToResponse();
await route.fulfill(); await route.fulfill({ _response });
} catch (e) { } catch (e) {
error = e; error = e;
} }
@ -115,13 +163,13 @@ it('should support request overrides', async ({page, server, browserName, browse
const requestPromise = server.waitForRequest('/empty.html'); const requestPromise = server.waitForRequest('/empty.html');
await page.route('**/foo', async route => { await page.route('**/foo', async route => {
// @ts-expect-error // @ts-expect-error
await route._intercept({ const _response = await route._continueToResponse({
url: server.EMPTY_PAGE, url: server.EMPTY_PAGE,
method: 'POST', method: 'POST',
headers: {'foo': 'bar'}, headers: {'foo': 'bar'},
postData: 'my data', postData: 'my data',
}); });
await route.fulfill(); await route.fulfill({ _response });
}); });
await page.goto(server.PREFIX + '/foo'); await page.goto(server.PREFIX + '/foo');
const request = await requestPromise; const request = await requestPromise;
@ -142,14 +190,14 @@ it('should give access to the intercepted response', async ({page, server}) => {
const route = await routePromise; const route = await routePromise;
// @ts-expect-error // @ts-expect-error
const response = await route._intercept(); const response = await route._continueToResponse();
expect(response.status()).toBe(200); expect(response.status()).toBe(200);
expect(response.ok()).toBeTruthy(); expect(response.ok()).toBeTruthy();
expect(response.url()).toBe(server.PREFIX + '/title.html'); expect(response.url()).toBe(server.PREFIX + '/title.html');
expect(response.headers()['content-type']).toBe('text/html; charset=utf-8'); 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}) => { 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 evalPromise = page.evaluate(url => fetch(url), server.PREFIX + '/title.html');
const route = await routePromise; const route = await routePromise;
// @ts-expect-error // @ts-expect-error
const response = await route._intercept(); const response = await route._continueToResponse();
expect(response.statusText()).toBe('You are awesome'); expect(response.statusText()).toBe('You are awesome');
expect(response.url()).toBe(server.PREFIX + '/title.html'); 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}) => { 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; const route = await routePromise;
// @ts-expect-error // @ts-expect-error
const response = await route._intercept(); const response = await route._continueToResponse();
expect((await response.text())).toBe('{"foo": "bar"}\n'); 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}) => { it('should be abortable after interception', async ({page, server, browserName}) => {
await page.route(/\.css$/, async route => { await page.route(/\.css$/, async route => {
// @ts-expect-error // @ts-expect-error
await route._intercept(); await route._continueToResponse();
await route.abort(); await route.abort();
}); });
let failed = false; let failed = false;
@ -224,7 +272,7 @@ it('should fulfill after redirects', async ({page, server, browserName}) => {
await page.route('**/*', async route => { await page.route('**/*', async route => {
++routeCalls; ++routeCalls;
// @ts-expect-error // @ts-expect-error
await route._intercept({}); await route._continueToResponse({});
await route.fulfill({ await route.fulfill({
status: 201, status: 201,
headers: { headers: {
@ -266,8 +314,8 @@ it('should fulfill original response after redirects', async ({page, browserName
await page.route('**/*', async route => { await page.route('**/*', async route => {
++routeCalls; ++routeCalls;
// @ts-expect-error // @ts-expect-error
await route._intercept({}); const _response = await route._continueToResponse({});
await route.fulfill(); await route.fulfill({ _response });
}); });
const response = await page.goto(server.PREFIX + '/redirect/1.html'); const response = await page.goto(server.PREFIX + '/redirect/1.html');
expect(requestUrls).toEqual(expectedUrls); expect(requestUrls).toEqual(expectedUrls);
@ -301,7 +349,7 @@ it('should abort after redirects', async ({page, browserName, server}) => {
await page.route('**/*', async route => { await page.route('**/*', async route => {
++routeCalls; ++routeCalls;
// @ts-expect-error // @ts-expect-error
await route._intercept({}); await route._continueToResponse({});
await route.abort('connectionreset'); await route.abort('connectionreset');
}); });

5
types/types.d.ts vendored
View file

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