feat(fetch): maxRetries for fetch (#31386)
Fixes https://github.com/microsoft/playwright/issues/30978
This commit is contained in:
parent
2dfda0a16f
commit
95fc2b8a8b
|
|
@ -344,6 +344,9 @@ If set changes the fetch method (e.g. [PUT](https://developer.mozilla.org/en-US/
|
||||||
### option: APIRequestContext.fetch.maxRedirects = %%-js-python-csharp-fetch-option-maxredirects-%%
|
### option: APIRequestContext.fetch.maxRedirects = %%-js-python-csharp-fetch-option-maxredirects-%%
|
||||||
* since: v1.26
|
* since: v1.26
|
||||||
|
|
||||||
|
### option: APIRequestContext.fetch.maxRetries = %%-js-python-csharp-fetch-option-maxretries-%%
|
||||||
|
* since: v1.46
|
||||||
|
|
||||||
## async method: APIRequestContext.get
|
## async method: APIRequestContext.get
|
||||||
* since: v1.16
|
* since: v1.16
|
||||||
- returns: <[APIResponse]>
|
- returns: <[APIResponse]>
|
||||||
|
|
@ -433,6 +436,9 @@ await request.GetAsync("https://example.com/api/getText", new() { Params = query
|
||||||
### option: APIRequestContext.get.maxRedirects = %%-js-python-csharp-fetch-option-maxredirects-%%
|
### option: APIRequestContext.get.maxRedirects = %%-js-python-csharp-fetch-option-maxredirects-%%
|
||||||
* since: v1.26
|
* since: v1.26
|
||||||
|
|
||||||
|
### option: APIRequestContext.get.maxRetries = %%-js-python-csharp-fetch-option-maxretries-%%
|
||||||
|
* since: v1.46
|
||||||
|
|
||||||
## async method: APIRequestContext.head
|
## async method: APIRequestContext.head
|
||||||
* since: v1.16
|
* since: v1.16
|
||||||
- returns: <[APIResponse]>
|
- returns: <[APIResponse]>
|
||||||
|
|
@ -486,6 +492,9 @@ context cookies from the response. The method will automatically follow redirect
|
||||||
### option: APIRequestContext.head.maxRedirects = %%-js-python-csharp-fetch-option-maxredirects-%%
|
### option: APIRequestContext.head.maxRedirects = %%-js-python-csharp-fetch-option-maxredirects-%%
|
||||||
* since: v1.26
|
* since: v1.26
|
||||||
|
|
||||||
|
### option: APIRequestContext.head.maxRetries = %%-js-python-csharp-fetch-option-maxretries-%%
|
||||||
|
* since: v1.46
|
||||||
|
|
||||||
## async method: APIRequestContext.patch
|
## async method: APIRequestContext.patch
|
||||||
* since: v1.16
|
* since: v1.16
|
||||||
- returns: <[APIResponse]>
|
- returns: <[APIResponse]>
|
||||||
|
|
@ -539,6 +548,9 @@ context cookies from the response. The method will automatically follow redirect
|
||||||
### option: APIRequestContext.patch.maxRedirects = %%-js-python-csharp-fetch-option-maxredirects-%%
|
### option: APIRequestContext.patch.maxRedirects = %%-js-python-csharp-fetch-option-maxredirects-%%
|
||||||
* since: v1.26
|
* since: v1.26
|
||||||
|
|
||||||
|
### option: APIRequestContext.patch.maxRetries = %%-js-python-csharp-fetch-option-maxretries-%%
|
||||||
|
* since: v1.46
|
||||||
|
|
||||||
## async method: APIRequestContext.post
|
## async method: APIRequestContext.post
|
||||||
* since: v1.16
|
* since: v1.16
|
||||||
- returns: <[APIResponse]>
|
- returns: <[APIResponse]>
|
||||||
|
|
@ -713,6 +725,9 @@ await request.PostAsync("https://example.com/api/uploadScript", new() { Multipar
|
||||||
### option: APIRequestContext.post.maxRedirects = %%-js-python-csharp-fetch-option-maxredirects-%%
|
### option: APIRequestContext.post.maxRedirects = %%-js-python-csharp-fetch-option-maxredirects-%%
|
||||||
* since: v1.26
|
* since: v1.26
|
||||||
|
|
||||||
|
### option: APIRequestContext.post.maxRetries = %%-js-python-csharp-fetch-option-maxretries-%%
|
||||||
|
* since: v1.46
|
||||||
|
|
||||||
## async method: APIRequestContext.put
|
## async method: APIRequestContext.put
|
||||||
* since: v1.16
|
* since: v1.16
|
||||||
- returns: <[APIResponse]>
|
- returns: <[APIResponse]>
|
||||||
|
|
@ -766,6 +781,9 @@ context cookies from the response. The method will automatically follow redirect
|
||||||
### option: APIRequestContext.put.maxRedirects = %%-js-python-csharp-fetch-option-maxredirects-%%
|
### option: APIRequestContext.put.maxRedirects = %%-js-python-csharp-fetch-option-maxredirects-%%
|
||||||
* since: v1.26
|
* since: v1.26
|
||||||
|
|
||||||
|
### option: APIRequestContext.put.maxRetries = %%-js-python-csharp-fetch-option-maxretries-%%
|
||||||
|
* since: v1.46
|
||||||
|
|
||||||
## async method: APIRequestContext.storageState
|
## async method: APIRequestContext.storageState
|
||||||
* since: v1.16
|
* since: v1.16
|
||||||
- returns: <[Object]>
|
- returns: <[Object]>
|
||||||
|
|
|
||||||
|
|
@ -126,6 +126,16 @@ Whether to ignore HTTPS errors when sending network requests.
|
||||||
Maximum number of request redirects that will be followed automatically. An error will be thrown if the number is exceeded.
|
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.
|
Defaults to `20`. Pass `0` to not follow redirects.
|
||||||
|
|
||||||
|
## method: RequestOptions.setMaxRetries
|
||||||
|
* since: v1.46
|
||||||
|
- returns: <[RequestOptions]>
|
||||||
|
|
||||||
|
### param: RequestOptions.setMaxRetries.maxRetries
|
||||||
|
* since: v1.46
|
||||||
|
- `maxRetries` <[int]>
|
||||||
|
|
||||||
|
Maximum number of times socket errors should be retried. Currently only `ECONNRESET` error is retried. An error will be thrown if the limit is exceeded. Defaults to `0` - no retries.
|
||||||
|
|
||||||
## method: RequestOptions.setMethod
|
## method: RequestOptions.setMethod
|
||||||
* since: v1.18
|
* since: v1.18
|
||||||
- returns: <[RequestOptions]>
|
- returns: <[RequestOptions]>
|
||||||
|
|
|
||||||
|
|
@ -458,6 +458,12 @@ Whether to ignore HTTPS errors when sending network requests. Defaults to `false
|
||||||
Maximum number of request redirects that will be followed automatically. An error will be thrown if the number is exceeded.
|
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.
|
Defaults to `20`. Pass `0` to not follow redirects.
|
||||||
|
|
||||||
|
## js-python-csharp-fetch-option-maxretries
|
||||||
|
* langs: js, python, csharp
|
||||||
|
- `maxRetries` <[int]>
|
||||||
|
|
||||||
|
Maximum number of times socket errors should be retried. Currently only `ECONNRESET` error is retried. An error will be thrown if the limit is exceeded. Defaults to `0` - no retries.
|
||||||
|
|
||||||
## evaluate-expression
|
## evaluate-expression
|
||||||
- `expression` <[string]>
|
- `expression` <[string]>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ export type FetchOptions = {
|
||||||
failOnStatusCode?: boolean,
|
failOnStatusCode?: boolean,
|
||||||
ignoreHTTPSErrors?: boolean,
|
ignoreHTTPSErrors?: boolean,
|
||||||
maxRedirects?: number,
|
maxRedirects?: number,
|
||||||
|
maxRetries?: number,
|
||||||
};
|
};
|
||||||
|
|
||||||
type NewContextOptions = Omit<channels.PlaywrightNewRequestOptions, 'extraHTTPHeaders' | 'storageState' | 'tracesDir'> & {
|
type NewContextOptions = Omit<channels.PlaywrightNewRequestOptions, 'extraHTTPHeaders' | 'storageState' | 'tracesDir'> & {
|
||||||
|
|
@ -168,11 +169,11 @@ export class APIRequestContext extends ChannelOwner<channels.APIRequestContextCh
|
||||||
throw new TargetClosedError(this._closeReason);
|
throw new TargetClosedError(this._closeReason);
|
||||||
assert(options.request || typeof options.url === 'string', 'First argument must be either URL string or Request');
|
assert(options.request || typeof options.url === '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.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'`);
|
assert(options.maxRedirects === undefined || options.maxRedirects >= 0, `'maxRedirects' must be greater than or equal to '0'`);
|
||||||
|
assert(options.maxRetries === undefined || options.maxRetries >= 0, `'maxRetries' must be greater than or equal to '0'`);
|
||||||
const url = options.url !== undefined ? options.url : options.request!.url();
|
const url = options.url !== undefined ? options.url : options.request!.url();
|
||||||
const params = objectToArray(options.params);
|
const params = objectToArray(options.params);
|
||||||
const method = options.method || options.request?.method();
|
const method = options.method || options.request?.method();
|
||||||
const maxRedirects = options.maxRedirects;
|
|
||||||
// Cannot call allHeaders() here as the request may be paused inside route handler.
|
// Cannot call allHeaders() here as the request may be paused inside route handler.
|
||||||
const headersObj = options.headers || options.request?.headers() ;
|
const headersObj = options.headers || options.request?.headers() ;
|
||||||
const headers = headersObj ? headersObjectToArray(headersObj) : undefined;
|
const headers = headersObj ? headersObjectToArray(headersObj) : undefined;
|
||||||
|
|
@ -234,7 +235,8 @@ export class APIRequestContext extends ChannelOwner<channels.APIRequestContextCh
|
||||||
timeout: options.timeout,
|
timeout: options.timeout,
|
||||||
failOnStatusCode: options.failOnStatusCode,
|
failOnStatusCode: options.failOnStatusCode,
|
||||||
ignoreHTTPSErrors: options.ignoreHTTPSErrors,
|
ignoreHTTPSErrors: options.ignoreHTTPSErrors,
|
||||||
maxRedirects: maxRedirects,
|
maxRedirects: options.maxRedirects,
|
||||||
|
maxRetries: options.maxRetries,
|
||||||
...fixtures
|
...fixtures
|
||||||
});
|
});
|
||||||
return new APIResponse(this, result.response);
|
return new APIResponse(this, result.response);
|
||||||
|
|
|
||||||
|
|
@ -336,7 +336,7 @@ export class Route extends ChannelOwner<channels.RouteChannel> implements api.Ro
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetch(options: FallbackOverrides & { maxRedirects?: number, timeout?: number } = {}): Promise<APIResponse> {
|
async fetch(options: FallbackOverrides & { maxRedirects?: number, maxRetries?: number, timeout?: number } = {}): Promise<APIResponse> {
|
||||||
return await this._wrapApiCall(async () => {
|
return await this._wrapApiCall(async () => {
|
||||||
return await this._context.request._innerFetch({ request: this.request(), data: options.postData, ...options });
|
return await this._context.request._innerFetch({ request: this.request(), data: options.postData, ...options });
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -182,6 +182,7 @@ scheme.APIRequestContextFetchParams = tObject({
|
||||||
failOnStatusCode: tOptional(tBoolean),
|
failOnStatusCode: tOptional(tBoolean),
|
||||||
ignoreHTTPSErrors: tOptional(tBoolean),
|
ignoreHTTPSErrors: tOptional(tBoolean),
|
||||||
maxRedirects: tOptional(tNumber),
|
maxRedirects: tOptional(tNumber),
|
||||||
|
maxRetries: tOptional(tNumber),
|
||||||
});
|
});
|
||||||
scheme.APIRequestContextFetchResult = tObject({
|
scheme.APIRequestContextFetchResult = tObject({
|
||||||
response: tType('APIResponse'),
|
response: tType('APIResponse'),
|
||||||
|
|
|
||||||
|
|
@ -201,7 +201,7 @@ export abstract class APIRequestContext extends SdkObject {
|
||||||
setHeader(headers, 'content-length', String(postData.byteLength));
|
setHeader(headers, 'content-length', String(postData.byteLength));
|
||||||
const controller = new ProgressController(metadata, this);
|
const controller = new ProgressController(metadata, this);
|
||||||
const fetchResponse = await controller.run(progress => {
|
const fetchResponse = await controller.run(progress => {
|
||||||
return this._sendRequest(progress, requestUrl, options, postData);
|
return this._sendRequestWithRetries(progress, requestUrl, options, postData, params.maxRetries);
|
||||||
});
|
});
|
||||||
const fetchUid = this._storeResponseBody(fetchResponse.body);
|
const fetchUid = this._storeResponseBody(fetchResponse.body);
|
||||||
this.fetchLog.set(fetchUid, controller.metadata.log);
|
this.fetchLog.set(fetchUid, controller.metadata.log);
|
||||||
|
|
@ -247,6 +247,28 @@ export abstract class APIRequestContext extends SdkObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async _sendRequestWithRetries(progress: Progress, url: URL, options: SendRequestOptions, postData?: Buffer, maxRetries?: number): Promise<Omit<channels.APIResponse, 'fetchUid'> & { body: Buffer }>{
|
||||||
|
maxRetries ??= 0;
|
||||||
|
let backoff = 250;
|
||||||
|
for (let i = 0; i <= maxRetries; i++) {
|
||||||
|
try {
|
||||||
|
return await this._sendRequest(progress, url, options, postData);
|
||||||
|
} catch (e) {
|
||||||
|
if (maxRetries === 0)
|
||||||
|
throw e;
|
||||||
|
if (i === maxRetries || (options.deadline && monotonicTime() + backoff > options.deadline))
|
||||||
|
throw new Error(`Failed after ${i + 1} attempt(s): ${e}`);
|
||||||
|
// Retry on connection reset only.
|
||||||
|
if (e.code !== 'ECONNRESET')
|
||||||
|
throw e;
|
||||||
|
progress.log(` Received ECONNRESET, will retry after ${backoff}ms.`);
|
||||||
|
await new Promise(f => setTimeout(f, backoff));
|
||||||
|
backoff *= 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Error('Unreachable');
|
||||||
|
}
|
||||||
|
|
||||||
private async _sendRequest(progress: Progress, url: URL, options: SendRequestOptions, postData?: Buffer): Promise<Omit<channels.APIResponse, 'fetchUid'> & { body: Buffer }>{
|
private async _sendRequest(progress: Progress, url: URL, options: SendRequestOptions, postData?: Buffer): Promise<Omit<channels.APIResponse, 'fetchUid'> & { body: Buffer }>{
|
||||||
await this._updateRequestCookieHeader(url, options.headers);
|
await this._updateRequestCookieHeader(url, options.headers);
|
||||||
|
|
||||||
|
|
|
||||||
36
packages/playwright-core/types/types.d.ts
vendored
36
packages/playwright-core/types/types.d.ts
vendored
|
|
@ -15963,6 +15963,12 @@ export interface APIRequestContext {
|
||||||
*/
|
*/
|
||||||
maxRedirects?: number;
|
maxRedirects?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum number of times socket errors should be retried. Currently only `ECONNRESET` error is retried. An error
|
||||||
|
* will be thrown if the limit is exceeded. Defaults to `0` - no retries.
|
||||||
|
*/
|
||||||
|
maxRetries?: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If set changes the fetch method (e.g. [PUT](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PUT) or
|
* 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.
|
* [POST](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST)). If not specified, GET method is used.
|
||||||
|
|
@ -16063,6 +16069,12 @@ export interface APIRequestContext {
|
||||||
*/
|
*/
|
||||||
maxRedirects?: number;
|
maxRedirects?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum number of times socket errors should be retried. Currently only `ECONNRESET` error is retried. An error
|
||||||
|
* will be thrown if the limit is exceeded. Defaults to `0` - no retries.
|
||||||
|
*/
|
||||||
|
maxRetries?: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides an object that will be serialized as html form using `multipart/form-data` encoding and sent as this
|
* 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
|
* request body. If this parameter is specified `content-type` header will be set to `multipart/form-data` unless
|
||||||
|
|
@ -16143,6 +16155,12 @@ export interface APIRequestContext {
|
||||||
*/
|
*/
|
||||||
maxRedirects?: number;
|
maxRedirects?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum number of times socket errors should be retried. Currently only `ECONNRESET` error is retried. An error
|
||||||
|
* will be thrown if the limit is exceeded. Defaults to `0` - no retries.
|
||||||
|
*/
|
||||||
|
maxRetries?: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides an object that will be serialized as html form using `multipart/form-data` encoding and sent as this
|
* 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
|
* request body. If this parameter is specified `content-type` header will be set to `multipart/form-data` unless
|
||||||
|
|
@ -16223,6 +16241,12 @@ export interface APIRequestContext {
|
||||||
*/
|
*/
|
||||||
maxRedirects?: number;
|
maxRedirects?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum number of times socket errors should be retried. Currently only `ECONNRESET` error is retried. An error
|
||||||
|
* will be thrown if the limit is exceeded. Defaults to `0` - no retries.
|
||||||
|
*/
|
||||||
|
maxRetries?: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides an object that will be serialized as html form using `multipart/form-data` encoding and sent as this
|
* 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
|
* request body. If this parameter is specified `content-type` header will be set to `multipart/form-data` unless
|
||||||
|
|
@ -16345,6 +16369,12 @@ export interface APIRequestContext {
|
||||||
*/
|
*/
|
||||||
maxRedirects?: number;
|
maxRedirects?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum number of times socket errors should be retried. Currently only `ECONNRESET` error is retried. An error
|
||||||
|
* will be thrown if the limit is exceeded. Defaults to `0` - no retries.
|
||||||
|
*/
|
||||||
|
maxRetries?: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides an object that will be serialized as html form using `multipart/form-data` encoding and sent as this
|
* 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
|
* request body. If this parameter is specified `content-type` header will be set to `multipart/form-data` unless
|
||||||
|
|
@ -16425,6 +16455,12 @@ export interface APIRequestContext {
|
||||||
*/
|
*/
|
||||||
maxRedirects?: number;
|
maxRedirects?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum number of times socket errors should be retried. Currently only `ECONNRESET` error is retried. An error
|
||||||
|
* will be thrown if the limit is exceeded. Defaults to `0` - no retries.
|
||||||
|
*/
|
||||||
|
maxRetries?: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides an object that will be serialized as html form using `multipart/form-data` encoding and sent as this
|
* 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
|
* request body. If this parameter is specified `content-type` header will be set to `multipart/form-data` unless
|
||||||
|
|
|
||||||
|
|
@ -324,6 +324,7 @@ export type APIRequestContextFetchParams = {
|
||||||
failOnStatusCode?: boolean,
|
failOnStatusCode?: boolean,
|
||||||
ignoreHTTPSErrors?: boolean,
|
ignoreHTTPSErrors?: boolean,
|
||||||
maxRedirects?: number,
|
maxRedirects?: number,
|
||||||
|
maxRetries?: number,
|
||||||
};
|
};
|
||||||
export type APIRequestContextFetchOptions = {
|
export type APIRequestContextFetchOptions = {
|
||||||
params?: NameValue[],
|
params?: NameValue[],
|
||||||
|
|
@ -337,6 +338,7 @@ export type APIRequestContextFetchOptions = {
|
||||||
failOnStatusCode?: boolean,
|
failOnStatusCode?: boolean,
|
||||||
ignoreHTTPSErrors?: boolean,
|
ignoreHTTPSErrors?: boolean,
|
||||||
maxRedirects?: number,
|
maxRedirects?: number,
|
||||||
|
maxRetries?: number,
|
||||||
};
|
};
|
||||||
export type APIRequestContextFetchResult = {
|
export type APIRequestContextFetchResult = {
|
||||||
response: APIResponse,
|
response: APIResponse,
|
||||||
|
|
|
||||||
|
|
@ -299,6 +299,7 @@ APIRequestContext:
|
||||||
failOnStatusCode: boolean?
|
failOnStatusCode: boolean?
|
||||||
ignoreHTTPSErrors: boolean?
|
ignoreHTTPSErrors: boolean?
|
||||||
maxRedirects: number?
|
maxRedirects: number?
|
||||||
|
maxRetries: number?
|
||||||
returns:
|
returns:
|
||||||
response: APIResponse
|
response: APIResponse
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1287,3 +1287,21 @@ it('should not work after context dispose', async ({ context, server }) => {
|
||||||
await context.close({ reason: 'Test ended.' });
|
await context.close({ reason: 'Test ended.' });
|
||||||
expect(await context.request.get(server.EMPTY_PAGE).catch(e => e.message)).toContain('Test ended.');
|
expect(await context.request.get(server.EMPTY_PAGE).catch(e => e.message)).toContain('Test ended.');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should retrty ECONNRESET', {
|
||||||
|
annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/30978' }
|
||||||
|
}, async ({ context, server }) => {
|
||||||
|
let requestCount = 0;
|
||||||
|
server.setRoute('/test', (req, res) => {
|
||||||
|
if (requestCount++ < 3) {
|
||||||
|
req.socket.destroy();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
res.writeHead(200, { 'content-type': 'text/plain' });
|
||||||
|
res.end('Hello!');
|
||||||
|
});
|
||||||
|
const response = await context.request.get(server.PREFIX + '/test', { maxRetries: 3 });
|
||||||
|
expect(response.status()).toBe(200);
|
||||||
|
expect(await response.text()).toBe('Hello!');
|
||||||
|
expect(requestCount).toBe(4);
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -436,7 +436,7 @@ it('should throw an error when maxRedirects is less than 0', async ({ playwright
|
||||||
|
|
||||||
const request = await playwright.request.newContext();
|
const request = await playwright.request.newContext();
|
||||||
for (const method of ['GET', 'PUT', 'POST', 'OPTIONS', 'HEAD', 'PATCH'])
|
for (const method of ['GET', 'PUT', 'POST', 'OPTIONS', 'HEAD', 'PATCH'])
|
||||||
await expect(async () => request.fetch(`${server.PREFIX}/a/redirect1`, { method, maxRedirects: -1 })).rejects.toThrow(`'maxRedirects' should be greater than or equal to '0'`);
|
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();
|
await request.dispose();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -483,3 +483,21 @@ it('should throw after dispose', async ({ playwright, server }) => {
|
||||||
await request.dispose();
|
await request.dispose();
|
||||||
await expect(request.get(server.EMPTY_PAGE)).rejects.toThrow('Target page, context or browser has been closed');
|
await expect(request.get(server.EMPTY_PAGE)).rejects.toThrow('Target page, context or browser has been closed');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should retry ECONNRESET', {
|
||||||
|
annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/30978' }
|
||||||
|
}, async ({ context, server }) => {
|
||||||
|
let requestCount = 0;
|
||||||
|
server.setRoute('/test', (req, res) => {
|
||||||
|
if (requestCount++ < 3) {
|
||||||
|
req.socket.destroy();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
res.writeHead(200, { 'content-type': 'text/plain' });
|
||||||
|
res.end('Hello!');
|
||||||
|
});
|
||||||
|
const response = await context.request.fetch(server.PREFIX + '/test', { maxRetries: 3 });
|
||||||
|
expect(response.status()).toBe(200);
|
||||||
|
expect(await response.text()).toBe('Hello!');
|
||||||
|
expect(requestCount).toBe(4);
|
||||||
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue