diff --git a/docs/src/api/class-fetchrequest.md b/docs/src/api/class-fetchrequest.md index 76a2cc36c5..1c61be2edf 100644 --- a/docs/src/api/class-fetchrequest.md +++ b/docs/src/api/class-fetchrequest.md @@ -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. diff --git a/src/client/fetch.ts b/src/client/fetch.ts index 0a4655df03..6239f000d4 100644 --- a/src/client/fetch.ts +++ b/src/client/fetch.ts @@ -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 { 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 { 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}`); diff --git a/src/dispatchers/browserContextDispatcher.ts b/src/dispatchers/browserContextDispatcher.ts index 8dce00a27a..dbb57c8044 100644 --- a/src/dispatchers/browserContextDispatcher.ts +++ b/src/dispatchers/browserContextDispatcher.ts @@ -115,6 +115,7 @@ export class BrowserContextDispatcher extends Dispatcher Validator): Scheme { headers: tOptional(tArray(tType('NameValue'))), postData: tOptional(tBinary), timeout: tOptional(tNumber), + failOnStatusCode: tOptional(tBoolean), }); scheme.BrowserContextFetchResponseBodyParams = tObject({ fetchUid: tString, diff --git a/src/server/fetch.ts b/src/server/fetch.ts index 097e4a3afc..a15dc9dcde 100644 --- a/src/server/fetch.ts +++ b/src/server/fetch.ts @@ -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) }; diff --git a/src/server/types.ts b/src/server/types.ts index edca190ecd..a34104a0f8 100644 --- a/src/server/types.ts +++ b/src/server/types.ts @@ -379,6 +379,7 @@ export type FetchOptions = { headers?: { [name: string]: string }, postData?: Buffer, timeout?: number, + failOnStatusCode?: boolean, }; export type FetchResponse = { diff --git a/tests/browsercontext-fetch.spec.ts b/tests/browsercontext-fetch.spec.ts index 29f750b0de..36fba61583 100644 --- a/tests/browsercontext-fetch.spec.ts +++ b/tests/browsercontext-fetch.spec.ts @@ -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([{ diff --git a/types/types.d.ts b/types/types.d.ts index 8a5d3d0f44..e198a778c1 100644 --- a/types/types.d.ts +++ b/types/types.d.ts @@ -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. */ diff --git a/utils/testserver/index.js b/utils/testserver/index.js index b7090bd60e..8142206028 100644 --- a/utils/testserver/index.js +++ b/utils/testserver/index.js @@ -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 {