Skip to content

Commit 5a76b17

Browse files
feat: add failOnStatusCode option to API request context (#34346)
1 parent 46b048f commit 5a76b17

File tree

9 files changed

+152
-1
lines changed

9 files changed

+152
-1
lines changed

docs/src/api/class-apirequest.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ Creates new instances of [APIRequestContext].
2121
### option: APIRequest.newContext.extraHTTPHeaders = %%-context-option-extrahttpheaders-%%
2222
* since: v1.16
2323

24+
### option: APIRequest.newContext.apiRequestFailsOnErrorStatus = %%-context-option-apiRequestFailsOnErrorStatus-%%
25+
* since: v1.51
26+
2427
### option: APIRequest.newContext.httpCredentials = %%-context-option-httpcredentials-%%
2528
* since: v1.16
2629

docs/src/api/params.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -639,6 +639,11 @@ A list of permissions to grant to all pages in this context. See
639639

640640
An object containing additional HTTP headers to be sent with every request. Defaults to none.
641641

642+
## context-option-apiRequestFailsOnErrorStatus
643+
- `apiRequestFailsOnErrorStatus` <[boolean]>
644+
645+
An object containing an option to throw an error when API request returns status codes other than 2xx and 3xx. By default, response object is returned for all status codes.
646+
642647
## context-option-offline
643648
- `offline` <[boolean]>
644649

@@ -996,6 +1001,7 @@ between the same pixel in compared images, between zero (strict) and one (lax),
9961001
- %%-context-option-locale-%%
9971002
- %%-context-option-permissions-%%
9981003
- %%-context-option-extrahttpheaders-%%
1004+
- %%-context-option-apiRequestFailsOnErrorStatus-%%
9991005
- %%-context-option-offline-%%
10001006
- %%-context-option-httpcredentials-%%
10011007
- %%-context-option-colorscheme-%%

packages/playwright-core/src/protocol/validator.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,7 @@ scheme.PlaywrightNewRequestParams = tObject({
371371
userAgent: tOptional(tString),
372372
ignoreHTTPSErrors: tOptional(tBoolean),
373373
extraHTTPHeaders: tOptional(tArray(tType('NameValue'))),
374+
apiRequestFailsOnErrorStatus: tOptional(tBoolean),
374375
clientCertificates: tOptional(tArray(tObject({
375376
origin: tString,
376377
cert: tOptional(tBinary),
@@ -600,6 +601,7 @@ scheme.BrowserTypeLaunchPersistentContextParams = tObject({
600601
})),
601602
permissions: tOptional(tArray(tString)),
602603
extraHTTPHeaders: tOptional(tArray(tType('NameValue'))),
604+
apiRequestFailsOnErrorStatus: tOptional(tBoolean),
603605
offline: tOptional(tBoolean),
604606
httpCredentials: tOptional(tObject({
605607
username: tString,
@@ -687,6 +689,7 @@ scheme.BrowserNewContextParams = tObject({
687689
})),
688690
permissions: tOptional(tArray(tString)),
689691
extraHTTPHeaders: tOptional(tArray(tType('NameValue'))),
692+
apiRequestFailsOnErrorStatus: tOptional(tBoolean),
690693
offline: tOptional(tBoolean),
691694
httpCredentials: tOptional(tObject({
692695
username: tString,
@@ -757,6 +760,7 @@ scheme.BrowserNewContextForReuseParams = tObject({
757760
})),
758761
permissions: tOptional(tArray(tString)),
759762
extraHTTPHeaders: tOptional(tArray(tType('NameValue'))),
763+
apiRequestFailsOnErrorStatus: tOptional(tBoolean),
760764
offline: tOptional(tBoolean),
761765
httpCredentials: tOptional(tObject({
762766
username: tString,
@@ -2664,6 +2668,7 @@ scheme.AndroidDeviceLaunchBrowserParams = tObject({
26642668
})),
26652669
permissions: tOptional(tArray(tString)),
26662670
extraHTTPHeaders: tOptional(tArray(tType('NameValue'))),
2671+
apiRequestFailsOnErrorStatus: tOptional(tBoolean),
26672672
offline: tOptional(tBoolean),
26682673
httpCredentials: tOptional(tObject({
26692674
username: tString,

packages/playwright-core/src/server/fetch.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ import type { Readable, TransformCallback } from 'stream';
5050
type FetchRequestOptions = {
5151
userAgent: string;
5252
extraHTTPHeaders?: HeadersArray;
53+
apiRequestFailsOnErrorStatus?: boolean;
5354
httpCredentials?: HTTPCredentials;
5455
proxy?: ProxySettings;
5556
timeoutSettings: TimeoutSettings;
@@ -210,7 +211,8 @@ export abstract class APIRequestContext extends SdkObject {
210211
});
211212
const fetchUid = this._storeResponseBody(fetchResponse.body);
212213
this.fetchLog.set(fetchUid, controller.metadata.log);
213-
if (params.failOnStatusCode && (fetchResponse.status < 200 || fetchResponse.status >= 400)) {
214+
const failOnStatusCode = params.failOnStatusCode !== undefined ? params.failOnStatusCode : !!defaults.apiRequestFailsOnErrorStatus;
215+
if (failOnStatusCode && (fetchResponse.status < 200 || fetchResponse.status >= 400)) {
214216
let responseText = '';
215217
if (fetchResponse.body.byteLength) {
216218
let text = fetchResponse.body.toString('utf8');
@@ -605,6 +607,7 @@ export class BrowserContextAPIRequestContext extends APIRequestContext {
605607
return {
606608
userAgent: this._context._options.userAgent || this._context._browser.userAgent(),
607609
extraHTTPHeaders: this._context._options.extraHTTPHeaders,
610+
apiRequestFailsOnErrorStatus: this._context._options.apiRequestFailsOnErrorStatus,
608611
httpCredentials: this._context._options.httpCredentials,
609612
proxy: this._context._options.proxy || this._context._browser.options.proxy,
610613
timeoutSettings: this._context._timeoutSettings,
@@ -656,6 +659,7 @@ export class GlobalAPIRequestContext extends APIRequestContext {
656659
baseURL: options.baseURL,
657660
userAgent: options.userAgent || getUserAgent(),
658661
extraHTTPHeaders: options.extraHTTPHeaders,
662+
apiRequestFailsOnErrorStatus: !!options.apiRequestFailsOnErrorStatus,
659663
ignoreHTTPSErrors: !!options.ignoreHTTPSErrors,
660664
httpCredentials: options.httpCredentials,
661665
clientCertificates: options.clientCertificates,

packages/playwright-core/types/types.d.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9742,6 +9742,12 @@ export interface Browser {
97429742
*/
97439743
acceptDownloads?: boolean;
97449744

9745+
/**
9746+
* An object containing an option to throw an error when API request returns status codes other than 2xx and 3xx. By
9747+
* default, response object is returned for all status codes.
9748+
*/
9749+
apiRequestFailsOnErrorStatus?: boolean;
9750+
97459751
/**
97469752
* When using [page.goto(url[, options])](https://playwright.dev/docs/api/class-page#page-goto),
97479753
* [page.route(url, handler[, options])](https://playwright.dev/docs/api/class-page#page-route),
@@ -14806,6 +14812,12 @@ export interface BrowserType<Unused = {}> {
1480614812
*/
1480714813
acceptDownloads?: boolean;
1480814814

14815+
/**
14816+
* An object containing an option to throw an error when API request returns status codes other than 2xx and 3xx. By
14817+
* default, response object is returned for all status codes.
14818+
*/
14819+
apiRequestFailsOnErrorStatus?: boolean;
14820+
1480914821
/**
1481014822
* **NOTE** Use custom browser args at your own risk, as some of them may break Playwright functionality.
1481114823
*
@@ -16696,6 +16708,12 @@ export interface AndroidDevice {
1669616708
*/
1669716709
acceptDownloads?: boolean;
1669816710

16711+
/**
16712+
* An object containing an option to throw an error when API request returns status codes other than 2xx and 3xx. By
16713+
* default, response object is returned for all status codes.
16714+
*/
16715+
apiRequestFailsOnErrorStatus?: boolean;
16716+
1669916717
/**
1670016718
* **NOTE** Use custom browser args at your own risk, as some of them may break Playwright functionality.
1670116719
*
@@ -17542,6 +17560,12 @@ export interface APIRequest {
1754217560
* @param options
1754317561
*/
1754417562
newContext(options?: {
17563+
/**
17564+
* An object containing an option to throw an error when API request returns status codes other than 2xx and 3xx. By
17565+
* default, response object is returned for all status codes.
17566+
*/
17567+
apiRequestFailsOnErrorStatus?: boolean;
17568+
1754517569
/**
1754617570
* Methods like
1754717571
* [apiRequestContext.get(url[, options])](https://playwright.dev/docs/api/class-apirequestcontext#api-request-context-get)
@@ -22117,6 +22141,12 @@ export interface BrowserContextOptions {
2211722141
*/
2211822142
acceptDownloads?: boolean;
2211922143

22144+
/**
22145+
* An object containing an option to throw an error when API request returns status codes other than 2xx and 3xx. By
22146+
* default, response object is returned for all status codes.
22147+
*/
22148+
apiRequestFailsOnErrorStatus?: boolean;
22149+
2212022150
/**
2212122151
* When using [page.goto(url[, options])](https://playwright.dev/docs/api/class-page#page-goto),
2212222152
* [page.route(url, handler[, options])](https://playwright.dev/docs/api/class-page#page-route),

packages/protocol/src/channels.d.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -623,6 +623,7 @@ export type PlaywrightNewRequestParams = {
623623
userAgent?: string,
624624
ignoreHTTPSErrors?: boolean,
625625
extraHTTPHeaders?: NameValue[],
626+
apiRequestFailsOnErrorStatus?: boolean,
626627
clientCertificates?: {
627628
origin: string,
628629
cert?: Binary,
@@ -654,6 +655,7 @@ export type PlaywrightNewRequestOptions = {
654655
userAgent?: string,
655656
ignoreHTTPSErrors?: boolean,
656657
extraHTTPHeaders?: NameValue[],
658+
apiRequestFailsOnErrorStatus?: boolean,
657659
clientCertificates?: {
658660
origin: string,
659661
cert?: Binary,
@@ -1027,6 +1029,7 @@ export type BrowserTypeLaunchPersistentContextParams = {
10271029
},
10281030
permissions?: string[],
10291031
extraHTTPHeaders?: NameValue[],
1032+
apiRequestFailsOnErrorStatus?: boolean,
10301033
offline?: boolean,
10311034
httpCredentials?: {
10321035
username: string,
@@ -1108,6 +1111,7 @@ export type BrowserTypeLaunchPersistentContextOptions = {
11081111
},
11091112
permissions?: string[],
11101113
extraHTTPHeaders?: NameValue[],
1114+
apiRequestFailsOnErrorStatus?: boolean,
11111115
offline?: boolean,
11121116
httpCredentials?: {
11131117
username: string,
@@ -1224,6 +1228,7 @@ export type BrowserNewContextParams = {
12241228
},
12251229
permissions?: string[],
12261230
extraHTTPHeaders?: NameValue[],
1231+
apiRequestFailsOnErrorStatus?: boolean,
12271232
offline?: boolean,
12281233
httpCredentials?: {
12291234
username: string,
@@ -1291,6 +1296,7 @@ export type BrowserNewContextOptions = {
12911296
},
12921297
permissions?: string[],
12931298
extraHTTPHeaders?: NameValue[],
1299+
apiRequestFailsOnErrorStatus?: boolean,
12941300
offline?: boolean,
12951301
httpCredentials?: {
12961302
username: string,
@@ -1361,6 +1367,7 @@ export type BrowserNewContextForReuseParams = {
13611367
},
13621368
permissions?: string[],
13631369
extraHTTPHeaders?: NameValue[],
1370+
apiRequestFailsOnErrorStatus?: boolean,
13641371
offline?: boolean,
13651372
httpCredentials?: {
13661373
username: string,
@@ -1428,6 +1435,7 @@ export type BrowserNewContextForReuseOptions = {
14281435
},
14291436
permissions?: string[],
14301437
extraHTTPHeaders?: NameValue[],
1438+
apiRequestFailsOnErrorStatus?: boolean,
14311439
offline?: boolean,
14321440
httpCredentials?: {
14331441
username: string,
@@ -4794,6 +4802,7 @@ export type AndroidDeviceLaunchBrowserParams = {
47944802
},
47954803
permissions?: string[],
47964804
extraHTTPHeaders?: NameValue[],
4805+
apiRequestFailsOnErrorStatus?: boolean,
47974806
offline?: boolean,
47984807
httpCredentials?: {
47994808
username: string,
@@ -4859,6 +4868,7 @@ export type AndroidDeviceLaunchBrowserOptions = {
48594868
},
48604869
permissions?: string[],
48614870
extraHTTPHeaders?: NameValue[],
4871+
apiRequestFailsOnErrorStatus?: boolean,
48624872
offline?: boolean,
48634873
httpCredentials?: {
48644874
username: string,

packages/protocol/src/protocol.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,7 @@ ContextOptions:
520520
extraHTTPHeaders:
521521
type: array?
522522
items: NameValue
523+
apiRequestFailsOnErrorStatus: boolean?
523524
offline: boolean?
524525
httpCredentials:
525526
type: object?
@@ -751,6 +752,7 @@ Playwright:
751752
extraHTTPHeaders:
752753
type: array?
753754
items: NameValue
755+
apiRequestFailsOnErrorStatus: boolean?
754756
clientCertificates:
755757
type: array?
756758
items:
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/**
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { browserTest as it, expect } from '../config/browserTest';
18+
19+
it('should throw when apiRequestFailsOnErrorStatus is set to true inside BrowserContext options', async ({ browser, server }) => {
20+
it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/34204' });
21+
const context = await browser.newContext({ apiRequestFailsOnErrorStatus: true });
22+
server.setRoute('/empty.html', (req, res) => {
23+
res.writeHead(404, { 'Content-Length': 10, 'Content-Type': 'text/plain' });
24+
res.end('Not found.');
25+
});
26+
const error = await context.request.fetch(server.EMPTY_PAGE).catch(e => e);
27+
expect(error.message).toContain('404 Not Found');
28+
await context.close();
29+
});
30+
31+
it('should not throw when failOnStatusCode is set to false inside BrowserContext options', async ({ browser, server }) => {
32+
it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/34204' });
33+
const context = await browser.newContext({ apiRequestFailsOnErrorStatus: false });
34+
server.setRoute('/empty.html', (req, res) => {
35+
res.writeHead(404, { 'Content-Length': 10, 'Content-Type': 'text/plain' });
36+
res.end('Not found.');
37+
});
38+
const error = await context.request.fetch(server.EMPTY_PAGE).catch(e => e);
39+
expect(error.message).toBeUndefined();
40+
await context.close();
41+
});
42+
43+
it('should throw when apiRequestFailsOnErrorStatus is set to true inside browserType.launchPersistentContext options', async ({ browserType, server, createUserDataDir }) => {
44+
it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/34204' });
45+
const userDataDir = await createUserDataDir();
46+
const context = await browserType.launchPersistentContext(userDataDir, { apiRequestFailsOnErrorStatus: true });
47+
server.setRoute('/empty.html', (req, res) => {
48+
res.writeHead(404, { 'Content-Length': 10, 'Content-Type': 'text/plain' });
49+
res.end('Not found.');
50+
});
51+
const error = await context.request.fetch(server.EMPTY_PAGE).catch(e => e);
52+
expect(error.message).toContain('404 Not Found');
53+
await context.close();
54+
});
55+
56+
it('should not throw when apiRequestFailsOnErrorStatus is set to false inside browserType.launchPersistentContext options', async ({ browserType, server, createUserDataDir }) => {
57+
it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/34204' });
58+
const userDataDir = await createUserDataDir();
59+
const context = await browserType.launchPersistentContext(userDataDir, { apiRequestFailsOnErrorStatus: false });
60+
server.setRoute('/empty.html', (req, res) => {
61+
res.writeHead(404, { 'Content-Length': 10, 'Content-Type': 'text/plain' });
62+
res.end('Not found.');
63+
});
64+
const error = await context.request.fetch(server.EMPTY_PAGE).catch(e => e);
65+
expect(error.message).toBeUndefined();
66+
await context.close();
67+
});

tests/library/global-fetch.spec.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,3 +536,27 @@ it('should retry ECONNRESET', {
536536
expect(requestCount).toBe(4);
537537
await request.dispose();
538538
});
539+
540+
it('should throw when apiRequestFailsOnErrorStatus is set to true inside APIRequest context options', async ({ playwright, server }) => {
541+
it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/34204' });
542+
const request = await playwright.request.newContext({ apiRequestFailsOnErrorStatus: true });
543+
server.setRoute('/empty.html', (req, res) => {
544+
res.writeHead(404, { 'Content-Length': 10, 'Content-Type': 'text/plain' });
545+
res.end('Not found.');
546+
});
547+
const error = await request.fetch(server.EMPTY_PAGE).catch(e => e);
548+
expect(error.message).toContain('404 Not Found');
549+
await request.dispose();
550+
});
551+
552+
it('should not throw when apiRequestFailsOnErrorStatus is set to false inside APIRequest context options', async ({ playwright, server }) => {
553+
it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/34204' });
554+
const request = await playwright.request.newContext({ apiRequestFailsOnErrorStatus: false });
555+
server.setRoute('/empty.html', (req, res) => {
556+
res.writeHead(404, { 'Content-Length': 10, 'Content-Type': 'text/plain' });
557+
res.end('Not found.');
558+
});
559+
const response = await request.fetch(server.EMPTY_PAGE);
560+
expect(response.status()).toBe(404);
561+
await request.dispose();
562+
});

0 commit comments

Comments
 (0)