feat: added follow and redirect arguments to fetch (#17033)

This commit is contained in:
Vincenzo Gasparo 2022-09-09 21:14:42 +02:00 committed by GitHub
parent 47b9595b95
commit 17b203affb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 96 additions and 2 deletions

View File

@ -161,6 +161,8 @@ context cookies from the response. The method will automatically follow redirect
* since: v1.16
### option: APIRequestContext.delete.ignoreHTTPSErrors = %%-js-python-fetch-option-ignorehttpserrors-%%
* since: v1.16
### option: APIRequestContext.delete.maxRedirects = %%-js-python-fetch-option-maxredirects-%%
* since: v1.26
## async method: APIRequestContext.dispose
* since: v1.16
@ -214,6 +216,8 @@ If set changes the fetch method (e.g. [PUT](https://developer.mozilla.org/en-US/
* since: v1.16
### option: APIRequestContext.fetch.ignoreHTTPSErrors = %%-js-python-fetch-option-ignorehttpserrors-%%
* since: v1.16
### option: APIRequestContext.fetch.maxRedirects = %%-js-python-fetch-option-maxredirects-%%
* since: v1.26
## async method: APIRequestContext.get
* since: v1.16
@ -239,6 +243,8 @@ context cookies from the response. The method will automatically follow redirect
* since: v1.16
### option: APIRequestContext.get.ignoreHTTPSErrors = %%-js-python-fetch-option-ignorehttpserrors-%%
* since: v1.16
### option: APIRequestContext.get.maxRedirects = %%-js-python-fetch-option-maxredirects-%%
* since: v1.26
## async method: APIRequestContext.head
* since: v1.16
@ -264,6 +270,8 @@ context cookies from the response. The method will automatically follow redirect
* since: v1.16
### option: APIRequestContext.head.ignoreHTTPSErrors = %%-js-python-fetch-option-ignorehttpserrors-%%
* since: v1.16
### option: APIRequestContext.head.maxRedirects = %%-js-python-fetch-option-maxredirects-%%
* since: v1.26
## async method: APIRequestContext.patch
* since: v1.16
@ -299,6 +307,8 @@ context cookies from the response. The method will automatically follow redirect
* since: v1.16
### option: APIRequestContext.patch.ignoreHTTPSErrors = %%-js-python-fetch-option-ignorehttpserrors-%%
* since: v1.16
### option: APIRequestContext.patch.maxRedirects = %%-js-python-fetch-option-maxredirects-%%
* since: v1.26
## async method: APIRequestContext.post
* since: v1.16
@ -334,6 +344,8 @@ context cookies from the response. The method will automatically follow redirect
* since: v1.16
### option: APIRequestContext.post.ignoreHTTPSErrors = %%-js-python-fetch-option-ignorehttpserrors-%%
* since: v1.16
### option: APIRequestContext.post.maxRedirects = %%-js-python-fetch-option-maxredirects-%%
* since: v1.26
## async method: APIRequestContext.put
* since: v1.16
@ -369,6 +381,8 @@ context cookies from the response. The method will automatically follow redirect
* since: v1.16
### option: APIRequestContext.put.ignoreHTTPSErrors = %%-js-python-fetch-option-ignorehttpserrors-%%
* since: v1.16
### option: APIRequestContext.put.maxRedirects = %%-js-python-fetch-option-maxredirects-%%
* since: v1.26
## async method: APIRequestContext.storageState
* since: v1.16

View File

@ -398,6 +398,12 @@ set to `application/octet-stream` if not explicitly set.
Whether to ignore HTTPS errors when sending network requests. Defaults to `false`.
## js-python-fetch-option-maxredirects
* langs: js
- `maxRedirects` <[int]>
Maximum number of request allowed redirects. Defaults to `20`. Pass `0` to disable automatic follow.
## evaluate-expression
- `expression` <[string]>

View File

@ -42,6 +42,7 @@ export type FetchOptions = {
timeout?: number,
failOnStatusCode?: boolean,
ignoreHTTPSErrors?: boolean,
maxRedirects?: number,
};
type NewContextOptions = Omit<channels.PlaywrightNewRequestOptions, 'extraHTTPHeaders' | 'storageState'> & {
@ -146,9 +147,11 @@ export class APIRequestContext extends ChannelOwner<channels.APIRequestContextCh
const request: network.Request | undefined = (urlOrRequest instanceof network.Request) ? urlOrRequest as network.Request : undefined;
assert(request || typeof urlOrRequest === 'string', 'First argument must be either URL string or Request');
assert((options.data === undefined ? 0 : 1) + (options.form === undefined ? 0 : 1) + (options.multipart === undefined ? 0 : 1) <= 1, `Only one of 'data', 'form' or 'multipart' can be specified`);
assert(options.maxRedirects === undefined || options.maxRedirects >= 0, `'maxRedirects' should be greater than or equal to '0'`);
const url = request ? request.url() : urlOrRequest as string;
const params = objectToArray(options.params);
const method = options.method || request?.method();
const maxRedirects = options.maxRedirects;
// Cannot call allHeaders() here as the request may be paused inside route handler.
const headersObj = options.headers || request?.headers() ;
const headers = headersObj ? headersObjectToArray(headersObj) : undefined;
@ -201,6 +204,7 @@ export class APIRequestContext extends ChannelOwner<channels.APIRequestContextCh
timeout: options.timeout,
failOnStatusCode: options.failOnStatusCode,
ignoreHTTPSErrors: options.ignoreHTTPSErrors,
maxRedirects: maxRedirects,
});
return new APIResponse(this, result.response);
});

View File

@ -310,6 +310,7 @@ export type APIRequestContextFetchParams = {
timeout?: number,
failOnStatusCode?: boolean,
ignoreHTTPSErrors?: boolean,
maxRedirects?: number,
};
export type APIRequestContextFetchOptions = {
params?: NameValue[],
@ -322,6 +323,7 @@ export type APIRequestContextFetchOptions = {
timeout?: number,
failOnStatusCode?: boolean,
ignoreHTTPSErrors?: boolean,
maxRedirects?: number,
};
export type APIRequestContextFetchResult = {
response: APIResponse,

View File

@ -284,6 +284,7 @@ APIRequestContext:
timeout: number?
failOnStatusCode: boolean?
ignoreHTTPSErrors: boolean?
maxRedirects: number?
returns:
response: APIResponse

View File

@ -171,6 +171,7 @@ scheme.APIRequestContextFetchParams = tObject({
timeout: tOptional(tNumber),
failOnStatusCode: tOptional(tBoolean),
ignoreHTTPSErrors: tOptional(tBoolean),
maxRedirects: tOptional(tNumber),
});
scheme.APIRequestContextFetchResult = tObject({
response: tType('APIResponse'),

View File

@ -162,7 +162,7 @@ export abstract class APIRequestContext extends SdkObject {
method,
headers,
agent,
maxRedirects: 20,
maxRedirects: params.maxRedirects === 0 ? -1 : params.maxRedirects === undefined ? 20 : params.maxRedirects,
timeout,
deadline
};
@ -268,7 +268,7 @@ export abstract class APIRequestContext extends SdkObject {
if (cookies.length)
await this._addCookies(cookies);
if (redirectStatus.includes(response.statusCode!)) {
if (redirectStatus.includes(response.statusCode!) && options.maxRedirects >= 0) {
if (!options.maxRedirects) {
reject(new Error('Max redirect count exceeded'));
request.destroy();

View File

@ -12667,6 +12667,11 @@ export interface APIRequestContext {
*/
ignoreHTTPSErrors?: boolean;
/**
* Maximum number of request allowed redirects. Defaults to `20`. Pass `0` to disable automatic follow.
*/
maxRedirects?: number;
/**
* Provides an object that will be serialized as html form using `multipart/form-data` encoding and sent as this request
* body. If this parameter is specified `content-type` header will be set to `multipart/form-data` unless explicitly
@ -12747,6 +12752,11 @@ export interface APIRequestContext {
*/
ignoreHTTPSErrors?: boolean;
/**
* Maximum number of request allowed redirects. Defaults to `20`. Pass `0` to disable automatic follow.
*/
maxRedirects?: number;
/**
* If set changes the fetch method (e.g. [PUT](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PUT) or
* [POST](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST)). If not specified, GET method is used.
@ -12810,6 +12820,11 @@ export interface APIRequestContext {
*/
ignoreHTTPSErrors?: boolean;
/**
* Maximum number of request allowed redirects. Defaults to `20`. Pass `0` to disable automatic follow.
*/
maxRedirects?: number;
/**
* Query parameters to be sent with the URL.
*/
@ -12844,6 +12859,11 @@ export interface APIRequestContext {
*/
ignoreHTTPSErrors?: boolean;
/**
* Maximum number of request allowed redirects. Defaults to `20`. Pass `0` to disable automatic follow.
*/
maxRedirects?: number;
/**
* Query parameters to be sent with the URL.
*/
@ -12892,6 +12912,11 @@ export interface APIRequestContext {
*/
ignoreHTTPSErrors?: boolean;
/**
* Maximum number of request allowed redirects. Defaults to `20`. Pass `0` to disable automatic follow.
*/
maxRedirects?: number;
/**
* Provides an object that will be serialized as html form using `multipart/form-data` encoding and sent as this request
* body. If this parameter is specified `content-type` header will be set to `multipart/form-data` unless explicitly
@ -12963,6 +12988,11 @@ export interface APIRequestContext {
*/
ignoreHTTPSErrors?: boolean;
/**
* Maximum number of request allowed redirects. Defaults to `20`. Pass `0` to disable automatic follow.
*/
maxRedirects?: number;
/**
* Provides an object that will be serialized as html form using `multipart/form-data` encoding and sent as this request
* body. If this parameter is specified `content-type` header will be set to `multipart/form-data` unless explicitly
@ -13034,6 +13064,11 @@ export interface APIRequestContext {
*/
ignoreHTTPSErrors?: boolean;
/**
* Maximum number of request allowed redirects. Defaults to `20`. Pass `0` to disable automatic follow.
*/
maxRedirects?: number;
/**
* Provides an object that will be serialized as html form using `multipart/form-data` encoding and sent as this request
* body. If this parameter is specified `content-type` header will be set to `multipart/form-data` unless explicitly

View File

@ -383,3 +383,34 @@ it('should return body for failing requests', async ({ playwright, server }) =>
}
await request.dispose();
});
it('should throw an error when maxRedirects is exceeded', async ({ playwright, server }) => {
server.setRedirect('/a/redirect1', '/b/c/redirect2');
server.setRedirect('/b/c/redirect2', '/b/c/redirect3');
server.setRedirect('/b/c/redirect3', '/b/c/redirect4');
server.setRedirect('/b/c/redirect4', '/simple.json');
const request = await playwright.request.newContext();
for (const method of ['GET', 'PUT', 'POST', 'OPTIONS', 'HEAD', 'PATCH'])
for (const maxRedirects of [1, 2, 3]) await expect(async () => request.fetch(`${server.PREFIX}/a/redirect1`, { method: method, maxRedirects: maxRedirects })).rejects.toThrow('Max redirect count exceeded');
});
it('should not follow redirects when maxRedirects is set to 0', async ({ playwright, server }) => {
server.setRedirect('/a/redirect1', '/b/c/redirect2');
server.setRedirect('/b/c/redirect2', '/simple.json');
const request = await playwright.request.newContext();
for (const method of ['GET', 'PUT', 'POST', 'OPTIONS', 'HEAD', 'PATCH']){
const response = await request.fetch(`${server.PREFIX}/a/redirect1`, { method: method, maxRedirects: 0 });
expect(response.headers()['location']).toBe('/b/c/redirect2');
expect(response.status()).toBe(302);
}
});
it('should throw an error when maxRedirects is less than 0', async ({ playwright, server }) => {
server.setRedirect('/a/redirect1', '/b/c/redirect2');
server.setRedirect('/b/c/redirect2', '/simple.json');
const request = await playwright.request.newContext();
for (const method of ['GET', 'PUT', 'POST', 'OPTIONS', 'HEAD', 'PATCH']) await expect(async () => request.fetch(`${server.PREFIX}/a/redirect1`, { method: method, maxRedirects: -1 })).rejects.toThrow(`'maxRedirects' should be greater than or equal to '0'`);
});