feat(fetch): introduce failOnStatusCode (#8896)

This commit is contained in:
Yury Semikhatsky 2021-09-13 15:38:27 -07:00 committed by GitHub
parent bb33b8923e
commit b79be5d98d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 73 additions and 18 deletions

View File

@ -41,6 +41,12 @@ Allows to set post data of the fetch.
Request timeout in milliseconds.
### option: FetchRequest.fetch.failOnStatusCode
- `failOnStatusCode` <[boolean]>
Whether to throw on response codes other than 2xx and 3xx. By default response object is returned
for all status codes.
## async method: FetchRequest.get
- returns: <[FetchResponse]>
@ -67,6 +73,12 @@ Allows to set HTTP headers.
Request timeout in milliseconds.
### option: FetchRequest.get.failOnStatusCode
- `failOnStatusCode` <[boolean]>
Whether to throw on response codes other than 2xx and 3xx. By default response object is returned
for all status codes.
## async method: FetchRequest.post
- returns: <[FetchResponse]>
@ -97,3 +109,9 @@ Allows to set post data of the fetch.
- `timeout` <[float]>
Request timeout in milliseconds.
### option: FetchRequest.post.failOnStatusCode
- `failOnStatusCode` <[boolean]>
Whether to throw on response codes other than 2xx and 3xx. By default response object is returned
for all status codes.

View File

@ -28,7 +28,8 @@ export type FetchOptions = {
method?: string,
headers?: Headers,
data?: string | Buffer,
timeout?: number
timeout?: number,
failOnStatusCode?: boolean,
};
export class FetchRequest implements api.FetchRequest {
@ -44,6 +45,7 @@ export class FetchRequest implements api.FetchRequest {
params?: { [key: string]: string; };
headers?: { [key: string]: string; };
timeout?: number;
failOnStatusCode?: boolean;
}): Promise<FetchResponse> {
return this.fetch(urlOrRequest, {
...options,
@ -58,6 +60,7 @@ export class FetchRequest implements api.FetchRequest {
headers?: { [key: string]: string; };
data?: string | Buffer;
timeout?: number;
failOnStatusCode?: boolean;
}): Promise<FetchResponse> {
return this.fetch(urlOrRequest, {
...options,
@ -86,6 +89,7 @@ export class FetchRequest implements api.FetchRequest {
headers,
postData,
timeout: options.timeout,
failOnStatusCode: options.failOnStatusCode,
});
if (result.error)
throw new Error(`Request failed: ${result.error}`);

View File

@ -115,6 +115,7 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channel
headers: params.headers ? headersArrayToObject(params.headers, false) : undefined,
postData: params.postData ? Buffer.from(params.postData, 'base64') : undefined,
timeout: params.timeout,
failOnStatusCode: params.failOnStatusCode,
});
let response;
if (fetchResponse) {

View File

@ -862,6 +862,7 @@ export type BrowserContextFetchParams = {
headers?: NameValue[],
postData?: Binary,
timeout?: number,
failOnStatusCode?: boolean,
};
export type BrowserContextFetchOptions = {
params?: NameValue[],
@ -869,6 +870,7 @@ export type BrowserContextFetchOptions = {
headers?: NameValue[],
postData?: Binary,
timeout?: number,
failOnStatusCode?: boolean,
};
export type BrowserContextFetchResult = {
response?: FetchResponse,

View File

@ -625,6 +625,7 @@ BrowserContext:
items: NameValue
postData: binary?
timeout: number?
failOnStatusCode: boolean?
returns:
response: FetchResponse?
error: string?

View File

@ -399,6 +399,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
headers: tOptional(tArray(tType('NameValue'))),
postData: tOptional(tBinary),
timeout: tOptional(tNumber),
failOnStatusCode: tOptional(tBoolean),
});
scheme.BrowserContextFetchResponseBodyParams = tObject({
fetchUid: tString,

View File

@ -74,6 +74,8 @@ export async function playwrightFetch(context: BrowserContext, params: types.Fet
const fetchResponse = await sendRequest(context, requestUrl, options, params.postData);
const fetchUid = context.storeFetchResponseBody(fetchResponse.body);
if (params.failOnStatusCode && (fetchResponse.status < 200 || fetchResponse.status >= 400))
return { error: `${fetchResponse.status} ${fetchResponse.statusText}` };
return { fetchResponse: { ...fetchResponse, fetchUid } };
} catch (e) {
return { error: String(e) };

View File

@ -379,6 +379,7 @@ export type FetchOptions = {
headers?: { [name: string]: string },
postData?: Buffer,
timeout?: number,
failOnStatusCode?: boolean,
};
export type FetchResponse = {

View File

@ -128,13 +128,16 @@ it('should add session cookies to request', async ({context, server}) => {
expect(req.headers.cookie).toEqual('username=John Doe');
});
it('should support queryParams', async ({context, server}) => {
let request;
server.setRoute('/empty.html', (req, res) => {
request = req;
server.serveFile(req, res);
});
for (const method of ['get', 'post', 'fetch']) {
for (const method of ['get', 'post', 'fetch']) {
it(`${method} should support queryParams`, async ({context, server}) => {
let request;
const url = new URL(server.EMPTY_PAGE);
url.searchParams.set('p1', 'v1');
url.searchParams.set('парам2', 'знач2');
server.setRoute(url.pathname + url.search, (req, res) => {
request = req;
server.serveFile(req, res);
});
await context.request[method](server.EMPTY_PAGE + '?p1=foo', {
params: {
'p1': 'v1',
@ -144,8 +147,15 @@ it('should support queryParams', async ({context, server}) => {
const params = new URLSearchParams(request.url.substr(request.url.indexOf('?')));
expect(params.get('p1')).toEqual('v1');
expect(params.get('парам2')).toEqual('знач2');
}
});
});
it(`${method} should support failOnStatusCode`, async ({context, server}) => {
const error = await context.request[method](server.PREFIX + '/does-not-exist.html', {
failOnStatusCode: true
}).catch(e => e);
expect(error.message).toContain('Request failed: 404 Not Found');
});
}
it('should not add context cookie if cookie header passed as a parameter', async ({context, server}) => {
await context.addCookies([{

15
types/types.d.ts vendored
View File

@ -12636,6 +12636,11 @@ export interface FetchRequest {
*/
data?: string|Buffer;
/**
* Whether to throw on response codes other than 2xx and 3xx. By default response object is returned for all status codes.
*/
failOnStatusCode?: boolean;
/**
* Allows to set HTTP headers.
*/
@ -12664,6 +12669,11 @@ export interface FetchRequest {
* @param options
*/
get(urlOrRequest: string|Request, options?: {
/**
* Whether to throw on response codes other than 2xx and 3xx. By default response object is returned for all status codes.
*/
failOnStatusCode?: boolean;
/**
* Allows to set HTTP headers.
*/
@ -12692,6 +12702,11 @@ export interface FetchRequest {
*/
data?: string|Buffer;
/**
* Whether to throw on response codes other than 2xx and 3xx. By default response object is returned for all status codes.
*/
failOnStatusCode?: boolean;
/**
* Allows to set HTTP headers.
*/

View File

@ -238,10 +238,10 @@ class TestServer {
request.on('data', chunk => body = Buffer.concat([body, chunk]));
request.on('end', () => resolve(body));
});
const pathName = url.parse(request.url).pathname;
this.debugServer(`request ${request.method} ${pathName}`);
if (this._auths.has(pathName)) {
const auth = this._auths.get(pathName);
const path = url.parse(request.url).path;
this.debugServer(`request ${request.method} ${path}`);
if (this._auths.has(path)) {
const auth = this._auths.get(path);
const credentials = Buffer.from((request.headers.authorization || '').split(' ')[1] || '', 'base64').toString();
this.debugServer(`request credentials ${credentials}`);
this.debugServer(`actual credentials ${auth.username}:${auth.password}`);
@ -253,11 +253,11 @@ class TestServer {
}
}
// Notify request subscriber.
if (this._requestSubscribers.has(pathName)) {
this._requestSubscribers.get(pathName)[fulfillSymbol].call(null, request);
this._requestSubscribers.delete(pathName);
if (this._requestSubscribers.has(path)) {
this._requestSubscribers.get(path)[fulfillSymbol].call(null, request);
this._requestSubscribers.delete(path);
}
const handler = this._routes.get(pathName);
const handler = this._routes.get(path);
if (handler) {
handler.call(null, request, response);
} else {