diff --git a/docs/src/api/class-apirequestcontext.md b/docs/src/api/class-apirequestcontext.md index 6812837b40..93e1d44e6e 100644 --- a/docs/src/api/class-apirequestcontext.md +++ b/docs/src/api/class-apirequestcontext.md @@ -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 diff --git a/docs/src/api/params.md b/docs/src/api/params.md index 65a177d3b0..100f13ad17 100644 --- a/docs/src/api/params.md +++ b/docs/src/api/params.md @@ -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]> diff --git a/packages/playwright-core/src/client/fetch.ts b/packages/playwright-core/src/client/fetch.ts index af43f91f47..185158a1a4 100644 --- a/packages/playwright-core/src/client/fetch.ts +++ b/packages/playwright-core/src/client/fetch.ts @@ -42,6 +42,7 @@ export type FetchOptions = { timeout?: number, failOnStatusCode?: boolean, ignoreHTTPSErrors?: boolean, + maxRedirects?: number, }; type NewContextOptions = Omit & { @@ -146,9 +147,11 @@ export class APIRequestContext extends ChannelOwner= 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= 0) { if (!options.maxRedirects) { reject(new Error('Max redirect count exceeded')); request.destroy(); diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts index 18cbcefec1..6b76347d5e 100644 --- a/packages/playwright-core/types/types.d.ts +++ b/packages/playwright-core/types/types.d.ts @@ -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 diff --git a/tests/library/global-fetch.spec.ts b/tests/library/global-fetch.spec.ts index f62880dd67..6f93084a7d 100644 --- a/tests/library/global-fetch.spec.ts +++ b/tests/library/global-fetch.spec.ts @@ -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'`); +}); \ No newline at end of file