feat(route): rename method, add response option (#8386)
This commit is contained in:
parent
20e4d9eee5
commit
59422a00f5
|
|
@ -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]>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 } = {}) {
|
||||||
|
|
|
||||||
|
|
@ -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 = {};
|
||||||
|
|
|
||||||
|
|
@ -2134,6 +2134,7 @@ Route:
|
||||||
items: NameValue
|
items: NameValue
|
||||||
body: string?
|
body: string?
|
||||||
isBase64: boolean?
|
isBase64: boolean?
|
||||||
|
useInterceptedResponseBody: boolean?
|
||||||
|
|
||||||
responseBody:
|
responseBody:
|
||||||
returns:
|
returns:
|
||||||
|
|
|
||||||
|
|
@ -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({
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -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
5
types/types.d.ts
vendored
|
|
@ -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.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue