Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add failOnStatusCode option to API request context #34346

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open
3 changes: 3 additions & 0 deletions docs/src/api/class-apirequest.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ Creates new instances of [APIRequestContext].
### option: APIRequest.newContext.extraHTTPHeaders = %%-context-option-extrahttpheaders-%%
* since: v1.16

### option: APIRequest.newContext.fetchFailOnStatusCode = %%-context-option-fetchFailOnStatusCode-%%
* since: v1.51

### option: APIRequest.newContext.httpCredentials = %%-context-option-httpcredentials-%%
* since: v1.16

Expand Down
6 changes: 6 additions & 0 deletions docs/src/api/params.md
Original file line number Diff line number Diff line change
Expand Up @@ -620,6 +620,11 @@ A list of permissions to grant to all pages in this context. See

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

## context-option-fetchFailOnStatusCode
- `fetchFailOnStatusCode` <[boolean]>

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.

## context-option-offline
- `offline` <[boolean]>

Expand Down Expand Up @@ -965,6 +970,7 @@ between the same pixel in compared images, between zero (strict) and one (lax),
- %%-context-option-locale-%%
- %%-context-option-permissions-%%
- %%-context-option-extrahttpheaders-%%
- %%-context-option-fetchFailOnStatusCode-%%
- %%-context-option-offline-%%
- %%-context-option-httpcredentials-%%
- %%-context-option-colorscheme-%%
Expand Down
6 changes: 5 additions & 1 deletion packages/playwright-core/src/server/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import { TLSSocket } from 'tls';
type FetchRequestOptions = {
userAgent: string;
extraHTTPHeaders?: HeadersArray;
fetchFailOnStatusCode?: boolean;
httpCredentials?: HTTPCredentials;
proxy?: ProxySettings;
timeoutSettings: TimeoutSettings;
Expand Down Expand Up @@ -205,7 +206,8 @@ export abstract class APIRequestContext extends SdkObject {
});
const fetchUid = this._storeResponseBody(fetchResponse.body);
this.fetchLog.set(fetchUid, controller.metadata.log);
if (params.failOnStatusCode && (fetchResponse.status < 200 || fetchResponse.status >= 400)) {
const failOnStatusCode = params.failOnStatusCode !== undefined ? params.failOnStatusCode : !!defaults.fetchFailOnStatusCode;
if (failOnStatusCode && (fetchResponse.status < 200 || fetchResponse.status >= 400)) {
let responseText = '';
if (fetchResponse.body.byteLength) {
let text = fetchResponse.body.toString('utf8');
Expand Down Expand Up @@ -610,6 +612,7 @@ export class BrowserContextAPIRequestContext extends APIRequestContext {
return {
userAgent: this._context._options.userAgent || this._context._browser.userAgent(),
extraHTTPHeaders: this._context._options.extraHTTPHeaders,
fetchFailOnStatusCode: this._context._options.fetchFailOnStatusCode,
httpCredentials: this._context._options.httpCredentials,
proxy: this._context._options.proxy || this._context._browser.options.proxy,
timeoutSettings: this._context._timeoutSettings,
Expand Down Expand Up @@ -661,6 +664,7 @@ export class GlobalAPIRequestContext extends APIRequestContext {
baseURL: options.baseURL,
userAgent: options.userAgent || getUserAgent(),
extraHTTPHeaders: options.extraHTTPHeaders,
fetchFailOnStatusCode: !!options.fetchFailOnStatusCode,
ignoreHTTPSErrors: !!options.ignoreHTTPSErrors,
httpCredentials: options.httpCredentials,
clientCertificates: options.clientCertificates,
Expand Down
2 changes: 2 additions & 0 deletions packages/protocol/src/protocol.yml
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,7 @@ ContextOptions:
extraHTTPHeaders:
type: array?
items: NameValue
fetchFailOnStatusCode: boolean?
offline: boolean?
httpCredentials:
type: object?
Expand Down Expand Up @@ -693,6 +694,7 @@ Playwright:
extraHTTPHeaders:
type: array?
items: NameValue
fetchFailOnStatusCode: boolean?
clientCertificates:
type: array?
items:
Expand Down
67 changes: 67 additions & 0 deletions tests/library/browsercontext-fetchFailOnStatusCode.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/**
* Copyright (c) Microsoft Corporation. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { browserTest as it, expect } from '../config/browserTest';

it('should throw when fetchFailOnStatusCode is set to true inside BrowserContext options', async ({ browser, server }) => {
it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/34204' });
const context = await browser.newContext({ fetchFailOnStatusCode: true });
server.setRoute('/empty.html', (req, res) => {
res.writeHead(404, { 'Content-Length': 10, 'Content-Type': 'text/plain' });
res.end('Not found.');
});
const error = await context.request.fetch(server.EMPTY_PAGE).catch(e => e);
expect(error.message).toContain('404 Not Found');
await context.close();
});

it('should not throw when failOnStatusCode is set to false inside BrowserContext options', async ({ browser, server }) => {
it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/34204' });
const context = await browser.newContext({ fetchFailOnStatusCode: false });
server.setRoute('/empty.html', (req, res) => {
res.writeHead(404, { 'Content-Length': 10, 'Content-Type': 'text/plain' });
res.end('Not found.');
});
const error = await context.request.fetch(server.EMPTY_PAGE).catch(e => e);
expect(error.message).toBeUndefined();
await context.close();
});

it('should throw when fetchFailOnStatusCode is set to true inside browserType.launchPersistentContext options', async ({ browserType, server, createUserDataDir }) => {
it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/34204' });
const userDataDir = await createUserDataDir();
const context = await browserType.launchPersistentContext(userDataDir, { fetchFailOnStatusCode: true });
server.setRoute('/empty.html', (req, res) => {
res.writeHead(404, { 'Content-Length': 10, 'Content-Type': 'text/plain' });
res.end('Not found.');
});
const error = await context.request.fetch(server.EMPTY_PAGE).catch(e => e);
expect(error.message).toContain('404 Not Found');
await context.close();
});

it('should not throw when fetchFailOnStatusCode is set to false inside browserType.launchPersistentContext options', async ({ browserType, server, createUserDataDir }) => {
it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/34204' });
const userDataDir = await createUserDataDir();
const context = await browserType.launchPersistentContext(userDataDir, { fetchFailOnStatusCode: false });
server.setRoute('/empty.html', (req, res) => {
res.writeHead(404, { 'Content-Length': 10, 'Content-Type': 'text/plain' });
res.end('Not found.');
});
const error = await context.request.fetch(server.EMPTY_PAGE).catch(e => e);
expect(error.message).toBeUndefined();
await context.close();
});
24 changes: 24 additions & 0 deletions tests/library/global-fetch.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -536,3 +536,27 @@ it('should retry ECONNRESET', {
expect(requestCount).toBe(4);
await request.dispose();
});

it('should throw when fetchFailOnStatusCode is set to true inside APIRequest context options', async ({ playwright, server }) => {
it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/34204' });
const request = await playwright.request.newContext({ fetchFailOnStatusCode: true });
server.setRoute('/empty.html', (req, res) => {
res.writeHead(404, { 'Content-Length': 10, 'Content-Type': 'text/plain' });
res.end('Not found.');
});
const error = await request.fetch(server.EMPTY_PAGE).catch(e => e);
expect(error.message).toContain('404 Not Found');
await request.dispose();
});

it('should not throw when fetchFailOnStatusCode is set to false inside APIRequest context options', async ({ playwright, server }) => {
it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/34204' });
const request = await playwright.request.newContext({ fetchFailOnStatusCode: false });
server.setRoute('/empty.html', (req, res) => {
res.writeHead(404, { 'Content-Length': 10, 'Content-Type': 'text/plain' });
res.end('Not found.');
});
const error = await request.fetch(server.EMPTY_PAGE).catch(e => e);
expect(error.message).toBeUndefined();
await request.dispose();
});
Loading