feat: Add maxRedirects to options of apiRequest.newContext (#35160)

This commit is contained in:
Chris 2025-03-17 14:47:52 +01:00 committed by GitHub
parent accd7c6c9e
commit beea7c30c5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 84 additions and 5 deletions

View File

@ -37,6 +37,13 @@ for all status codes.
### option: APIRequest.newContext.ignoreHTTPSErrors = %%-context-option-ignorehttpserrors-%%
* since: v1.16
### option: APIRequest.newContext.maxRedirects
* since: v1.52
- `maxRedirects` <[int]>
Maximum number of request redirects that will be followed automatically. An error will be thrown if the number is exceeded.
Defaults to `20`. Pass `0` to not follow redirects. This can be overwritten for each request individually.
### option: APIRequest.newContext.timeout
* since: v1.16
- `timeout` <[float]>

View File

@ -17573,6 +17573,13 @@ export interface APIRequest {
*/
ignoreHTTPSErrors?: boolean;
/**
* Maximum number of request redirects that will be followed automatically. An error will be thrown if the number is
* exceeded. Defaults to `20`. Pass `0` to not follow redirects. This can be overwritten for each request
* individually.
*/
maxRedirects?: number;
/**
* Network proxy settings.
*/

View File

@ -386,6 +386,7 @@ scheme.PlaywrightNewRequestParams = tObject({
passphrase: tOptional(tString),
pfx: tOptional(tBinary),
}))),
maxRedirects: tOptional(tNumber),
httpCredentials: tOptional(tObject({
username: tString,
password: tString,

View File

@ -56,6 +56,7 @@ type FetchRequestOptions = {
proxy?: ProxySettings;
timeoutSettings: TimeoutSettings;
ignoreHTTPSErrors?: boolean;
maxRedirects?: number;
baseURL?: string;
clientCertificates?: types.BrowserContextOptions['clientCertificates'];
};
@ -185,6 +186,8 @@ export abstract class APIRequestContext extends SdkObject {
if (proxy && proxy.server !== 'per-context' && !shouldBypassProxy(requestUrl, proxy.bypass))
agent = createProxyAgent(proxy);
let maxRedirects = params.maxRedirects ?? (defaults.maxRedirects ?? 20);
maxRedirects = maxRedirects === 0 ? -1 : maxRedirects;
const timeout = defaults.timeoutSettings.timeout(params);
const deadline = timeout && (monotonicTime() + timeout);
@ -193,7 +196,7 @@ export abstract class APIRequestContext extends SdkObject {
method,
headers,
agent,
maxRedirects: params.maxRedirects === 0 ? -1 : params.maxRedirects === undefined ? 20 : params.maxRedirects,
maxRedirects,
timeout,
deadline,
...getMatchingTLSOptionsForOrigin(this._defaultOptions().clientCertificates, requestUrl.origin),
@ -371,7 +374,7 @@ export abstract class APIRequestContext extends SdkObject {
}
if (redirectStatus.includes(response.statusCode!) && options.maxRedirects >= 0) {
if (!options.maxRedirects) {
if (options.maxRedirects === 0) {
reject(new Error('Max redirect count exceeded'));
request.destroy();
return;
@ -662,6 +665,7 @@ export class GlobalAPIRequestContext extends APIRequestContext {
extraHTTPHeaders: options.extraHTTPHeaders,
failOnStatusCode: !!options.failOnStatusCode,
ignoreHTTPSErrors: !!options.ignoreHTTPSErrors,
maxRedirects: options.maxRedirects,
httpCredentials: options.httpCredentials,
clientCertificates: options.clientCertificates,
proxy,

View File

@ -17573,6 +17573,13 @@ export interface APIRequest {
*/
ignoreHTTPSErrors?: boolean;
/**
* Maximum number of request redirects that will be followed automatically. An error will be thrown if the number is
* exceeded. Defaults to `20`. Pass `0` to not follow redirects. This can be overwritten for each request
* individually.
*/
maxRedirects?: number;
/**
* Network proxy settings.
*/

View File

@ -644,6 +644,7 @@ export type PlaywrightNewRequestParams = {
passphrase?: string,
pfx?: Binary,
}[],
maxRedirects?: number,
httpCredentials?: {
username: string,
password: string,
@ -676,6 +677,7 @@ export type PlaywrightNewRequestOptions = {
passphrase?: string,
pfx?: Binary,
}[],
maxRedirects?: number,
httpCredentials?: {
username: string,
password: string,

View File

@ -770,6 +770,7 @@ Playwright:
key: binary?
passphrase: string?
pfx: binary?
maxRedirects: number?
httpCredentials:
type: object?
properties:

View File

@ -437,6 +437,8 @@ it('should return body for failing requests', async ({ playwright, server }) =>
await request.dispose();
});
const HTTP_METHODS = ['GET', 'PUT', 'POST', 'OPTIONS', 'HEAD', 'PATCH'] as const;
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');
@ -444,7 +446,7 @@ it('should throw an error when maxRedirects is exceeded', async ({ playwright, s
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 method of HTTP_METHODS) {
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');
}
@ -456,7 +458,7 @@ it('should not follow redirects when maxRedirects is set to 0', async ({ playwri
server.setRedirect('/b/c/redirect2', '/simple.json');
const request = await playwright.request.newContext();
for (const method of ['GET', 'PUT', 'POST', 'OPTIONS', 'HEAD', 'PATCH']){
for (const method of HTTP_METHODS){
const response = await request.fetch(`${server.PREFIX}/a/redirect1`, { method, maxRedirects: 0 });
expect(response.headers()['location']).toBe('/b/c/redirect2');
expect(response.status()).toBe(302);
@ -469,11 +471,59 @@ it('should throw an error when maxRedirects is less than 0', async ({ playwright
server.setRedirect('/b/c/redirect2', '/simple.json');
const request = await playwright.request.newContext();
for (const method of ['GET', 'PUT', 'POST', 'OPTIONS', 'HEAD', 'PATCH'])
for (const method of HTTP_METHODS)
await expect(async () => request.fetch(`${server.PREFIX}/a/redirect1`, { method, maxRedirects: -1 })).rejects.toThrow(`'maxRedirects' must be greater than or equal to '0'`);
await request.dispose();
});
it('should not follow redirects when maxRedirects is set to 0 in newContext', async ({ playwright, server }) => {
server.setRedirect('/a/redirect1', '/b/c/redirect2');
server.setRedirect('/b/c/redirect2', '/simple.json');
const request = await playwright.request.newContext({ maxRedirects: 0 });
for (const method of HTTP_METHODS) {
const response = await request.fetch(`${server.PREFIX}/a/redirect1`, { method });
expect(response.headers()['location']).toBe('/b/c/redirect2');
expect(response.status()).toBe(302);
}
await request.dispose();
});
it('should follow redirects up to maxRedirects limit set in newContext', 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');
for (const maxRedirects of [1, 2, 3, 4]) {
const request = await playwright.request.newContext({ maxRedirects });
for (const method of HTTP_METHODS) {
if (maxRedirects < 4) {
await expect(async () => request.fetch(`${server.PREFIX}/a/redirect1`, { method }))
.rejects.toThrow('Max redirect count exceeded');
} else {
const response = await request.fetch(`${server.PREFIX}/a/redirect1`, { method });
expect(response.status()).toBe(200);
}
}
await request.dispose();
}
});
it('should use maxRedirects from fetch when provided, overriding newContext', 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({ maxRedirects: 1 });
for (const method of HTTP_METHODS) {
const response = await request.fetch(`${server.PREFIX}/a/redirect1`, { method, maxRedirects: 4 });
expect(response.status()).toBe(200);
}
await request.dispose();
});
it('should keep headers capitalization', async ({ playwright, server }) => {
const request = await playwright.request.newContext();
const [serverRequest, response] = await Promise.all([