diff --git a/docs/src/api/params.md b/docs/src/api/params.md index ffff0a800e..a1dd84c4ef 100644 --- a/docs/src/api/params.md +++ b/docs/src/api/params.md @@ -571,7 +571,7 @@ Whether to emulate network being offline. Defaults to `false`. Learn more about - `username` <[string]> - `password` <[string]> - `origin` ?<[string]> Restrain sending http credentials on specific origin (scheme://host:port). - - `sendImmediately` ?<[boolean]> Whether to send `Authorization` header with the first API request. By default, the credentials are sent only when 401 (Unauthorized) response with `WWW-Authenticate` header is received. This option does not affect requests sent from the browser. + - `send` ?<[HttpCredentialsSend]<"unauthorized"|"always">> This option only applies to the requests sent from corresponding [APIRequestContext] and does not affect requests sent from the browser. `'always'` - `Authorization` header with basic authentication credentials will be sent with the each API request. `'unauthorized` - the credentials are only sent when 401 (Unauthorized) response with `WWW-Authenticate` header is received. Defaults to `'unauthorized'`. Credentials for [HTTP authentication](https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication). If no origin is specified, the username and password are sent to any servers upon unauthorized responses. diff --git a/packages/playwright-core/src/protocol/validator.ts b/packages/playwright-core/src/protocol/validator.ts index 4d505069e1..30c7856ca5 100644 --- a/packages/playwright-core/src/protocol/validator.ts +++ b/packages/playwright-core/src/protocol/validator.ts @@ -334,7 +334,7 @@ scheme.PlaywrightNewRequestParams = tObject({ username: tString, password: tString, origin: tOptional(tString), - sendImmediately: tOptional(tBoolean), + send: tOptional(tEnum(['always', 'unauthorized'])), })), proxy: tOptional(tObject({ server: tString, @@ -548,7 +548,7 @@ scheme.BrowserTypeLaunchPersistentContextParams = tObject({ username: tString, password: tString, origin: tOptional(tString), - sendImmediately: tOptional(tBoolean), + send: tOptional(tEnum(['always', 'unauthorized'])), })), deviceScaleFactor: tOptional(tNumber), isMobile: tOptional(tBoolean), @@ -627,7 +627,7 @@ scheme.BrowserNewContextParams = tObject({ username: tString, password: tString, origin: tOptional(tString), - sendImmediately: tOptional(tBoolean), + send: tOptional(tEnum(['always', 'unauthorized'])), })), deviceScaleFactor: tOptional(tNumber), isMobile: tOptional(tBoolean), @@ -689,7 +689,7 @@ scheme.BrowserNewContextForReuseParams = tObject({ username: tString, password: tString, origin: tOptional(tString), - sendImmediately: tOptional(tBoolean), + send: tOptional(tEnum(['always', 'unauthorized'])), })), deviceScaleFactor: tOptional(tNumber), isMobile: tOptional(tBoolean), @@ -2506,7 +2506,7 @@ scheme.AndroidDeviceLaunchBrowserParams = tObject({ username: tString, password: tString, origin: tOptional(tString), - sendImmediately: tOptional(tBoolean), + send: tOptional(tEnum(['always', 'unauthorized'])), })), deviceScaleFactor: tOptional(tNumber), isMobile: tOptional(tBoolean), diff --git a/packages/playwright-core/src/server/fetch.ts b/packages/playwright-core/src/server/fetch.ts index e15c5aca8a..0f87e0046b 100644 --- a/packages/playwright-core/src/server/fetch.ts +++ b/packages/playwright-core/src/server/fetch.ts @@ -159,7 +159,7 @@ export abstract class APIRequestContext extends SdkObject { } const credentials = this._getHttpCredentials(requestUrl); - if (credentials?.sendImmediately) + if (credentials?.send === 'always') setBasicAuthorizationHeader(headers, credentials); const method = params.method?.toUpperCase() || 'GET'; diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts index 38cdd75a58..0f88ed92c5 100644 --- a/packages/playwright-core/types/types.d.ts +++ b/packages/playwright-core/types/types.d.ts @@ -13393,11 +13393,12 @@ export interface BrowserType { origin?: string; /** - * Whether to send `Authorization` header with the first API request. By default, the credentials are sent only when - * 401 (Unauthorized) response with `WWW-Authenticate` header is received. This option does not affect requests sent - * from the browser. + * This option only applies to the requests sent from corresponding {@link APIRequestContext} and does not affect + * requests sent from the browser. `'always'` - `Authorization` header with basic authentication credentials will be + * sent with the each API request. `'unauthorized` - the credentials are only sent when 401 (Unauthorized) response + * with `WWW-Authenticate` header is received. Defaults to `'unauthorized'`. */ - sendImmediately?: boolean; + send?: "unauthorized"|"always"; }; /** @@ -14930,11 +14931,12 @@ export interface AndroidDevice { origin?: string; /** - * Whether to send `Authorization` header with the first API request. By default, the credentials are sent only when - * 401 (Unauthorized) response with `WWW-Authenticate` header is received. This option does not affect requests sent - * from the browser. + * This option only applies to the requests sent from corresponding {@link APIRequestContext} and does not affect + * requests sent from the browser. `'always'` - `Authorization` header with basic authentication credentials will be + * sent with the each API request. `'unauthorized` - the credentials are only sent when 401 (Unauthorized) response + * with `WWW-Authenticate` header is received. Defaults to `'unauthorized'`. */ - sendImmediately?: boolean; + send?: "unauthorized"|"always"; }; /** @@ -15661,11 +15663,12 @@ export interface APIRequest { origin?: string; /** - * Whether to send `Authorization` header with the first API request. By default, the credentials are sent only when - * 401 (Unauthorized) response with `WWW-Authenticate` header is received. This option does not affect requests sent - * from the browser. + * This option only applies to the requests sent from corresponding {@link APIRequestContext} and does not affect + * requests sent from the browser. `'always'` - `Authorization` header with basic authentication credentials will be + * sent with the each API request. `'unauthorized` - the credentials are only sent when 401 (Unauthorized) response + * with `WWW-Authenticate` header is received. Defaults to `'unauthorized'`. */ - sendImmediately?: boolean; + send?: "unauthorized"|"always"; }; /** @@ -16818,11 +16821,12 @@ export interface Browser extends EventEmitter { origin?: string; /** - * Whether to send `Authorization` header with the first API request. By default, the credentials are sent only when - * 401 (Unauthorized) response with `WWW-Authenticate` header is received. This option does not affect requests sent - * from the browser. + * This option only applies to the requests sent from corresponding {@link APIRequestContext} and does not affect + * requests sent from the browser. `'always'` - `Authorization` header with basic authentication credentials will be + * sent with the each API request. `'unauthorized` - the credentials are only sent when 401 (Unauthorized) response + * with `WWW-Authenticate` header is received. Defaults to `'unauthorized'`. */ - sendImmediately?: boolean; + send?: "unauthorized"|"always"; }; /** @@ -17790,11 +17794,12 @@ export interface Electron { origin?: string; /** - * Whether to send `Authorization` header with the first API request. By default, the credentials are sent only when - * 401 (Unauthorized) response with `WWW-Authenticate` header is received. This option does not affect requests sent - * from the browser. + * This option only applies to the requests sent from corresponding {@link APIRequestContext} and does not affect + * requests sent from the browser. `'always'` - `Authorization` header with basic authentication credentials will be + * sent with the each API request. `'unauthorized` - the credentials are only sent when 401 (Unauthorized) response + * with `WWW-Authenticate` header is received. Defaults to `'unauthorized'`. */ - sendImmediately?: boolean; + send?: "unauthorized"|"always"; }; /** @@ -20458,11 +20463,12 @@ export interface HTTPCredentials { origin?: string; /** - * Whether to send `Authorization` header with the first API request. By default, the credentials are sent only when - * 401 (Unauthorized) response with `WWW-Authenticate` header is received. This option does not affect requests sent - * from the browser. + * This option only applies to the requests sent from corresponding {@link APIRequestContext} and does not affect + * requests sent from the browser. `'always'` - `Authorization` header with basic authentication credentials will be + * sent with the each API request. `'unauthorized` - the credentials are only sent when 401 (Unauthorized) response + * with `WWW-Authenticate` header is received. Defaults to `'unauthorized'`. */ - sendImmediately?: boolean; + send?: "unauthorized"|"always"; } export interface Geolocation { diff --git a/packages/protocol/src/channels.ts b/packages/protocol/src/channels.ts index 8ad202ca16..df8658ee0d 100644 --- a/packages/protocol/src/channels.ts +++ b/packages/protocol/src/channels.ts @@ -578,7 +578,7 @@ export type PlaywrightNewRequestParams = { username: string, password: string, origin?: string, - sendImmediately?: boolean, + send?: 'always' | 'unauthorized', }, proxy?: { server: string, @@ -602,7 +602,7 @@ export type PlaywrightNewRequestOptions = { username: string, password: string, origin?: string, - sendImmediately?: boolean, + send?: 'always' | 'unauthorized', }, proxy?: { server: string, @@ -959,7 +959,7 @@ export type BrowserTypeLaunchPersistentContextParams = { username: string, password: string, origin?: string, - sendImmediately?: boolean, + send?: 'always' | 'unauthorized', }, deviceScaleFactor?: number, isMobile?: boolean, @@ -1032,7 +1032,7 @@ export type BrowserTypeLaunchPersistentContextOptions = { username: string, password: string, origin?: string, - sendImmediately?: boolean, + send?: 'always' | 'unauthorized', }, deviceScaleFactor?: number, isMobile?: boolean, @@ -1140,7 +1140,7 @@ export type BrowserNewContextParams = { username: string, password: string, origin?: string, - sendImmediately?: boolean, + send?: 'always' | 'unauthorized', }, deviceScaleFactor?: number, isMobile?: boolean, @@ -1199,7 +1199,7 @@ export type BrowserNewContextOptions = { username: string, password: string, origin?: string, - sendImmediately?: boolean, + send?: 'always' | 'unauthorized', }, deviceScaleFactor?: number, isMobile?: boolean, @@ -1261,7 +1261,7 @@ export type BrowserNewContextForReuseParams = { username: string, password: string, origin?: string, - sendImmediately?: boolean, + send?: 'always' | 'unauthorized', }, deviceScaleFactor?: number, isMobile?: boolean, @@ -1320,7 +1320,7 @@ export type BrowserNewContextForReuseOptions = { username: string, password: string, origin?: string, - sendImmediately?: boolean, + send?: 'always' | 'unauthorized', }, deviceScaleFactor?: number, isMobile?: boolean, @@ -4529,7 +4529,7 @@ export type AndroidDeviceLaunchBrowserParams = { username: string, password: string, origin?: string, - sendImmediately?: boolean, + send?: 'always' | 'unauthorized', }, deviceScaleFactor?: number, isMobile?: boolean, @@ -4586,7 +4586,7 @@ export type AndroidDeviceLaunchBrowserOptions = { username: string, password: string, origin?: string, - sendImmediately?: boolean, + send?: 'always' | 'unauthorized', }, deviceScaleFactor?: number, isMobile?: boolean, diff --git a/packages/protocol/src/protocol.yml b/packages/protocol/src/protocol.yml index 34020e1212..96a511d306 100644 --- a/packages/protocol/src/protocol.yml +++ b/packages/protocol/src/protocol.yml @@ -456,7 +456,11 @@ ContextOptions: username: string password: string origin: string? - sendImmediately: boolean? + send: + type: enum? + literals: + - always + - unauthorized deviceScaleFactor: number? isMobile: boolean? hasTouch: boolean? @@ -674,7 +678,11 @@ Playwright: username: string password: string origin: string? - sendImmediately: boolean? + send: + type: enum? + literals: + - always + - unauthorized proxy: type: object? properties: diff --git a/tests/library/browsercontext-fetch.spec.ts b/tests/library/browsercontext-fetch.spec.ts index 99fd0265a3..ba01ccc07c 100644 --- a/tests/library/browsercontext-fetch.spec.ts +++ b/tests/library/browsercontext-fetch.spec.ts @@ -435,10 +435,10 @@ it('should return error with wrong credentials', async ({ context, server }) => expect(response2.status()).toBe(401); }); -it('should support HTTPCredentials.sendImmediately', async ({ contextFactory, server }) => { +it('should support HTTPCredentials.sendImmediately for newContext', async ({ contextFactory, server }) => { it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/30534' }); const context = await contextFactory({ - httpCredentials: { username: 'user', password: 'pass', origin: server.PREFIX.toUpperCase(), sendImmediately: true } + httpCredentials: { username: 'user', password: 'pass', origin: server.PREFIX.toUpperCase(), send: 'always' } }); { const [serverRequest, response] = await Promise.all([ @@ -459,6 +459,31 @@ it('should support HTTPCredentials.sendImmediately', async ({ contextFactory, se } }); +it('should support HTTPCredentials.sendImmediately for browser.newPage', async ({ contextFactory, server, browser }) => { + it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/30534' }); + const page = await browser.newPage({ + httpCredentials: { username: 'user', password: 'pass', origin: server.PREFIX.toUpperCase(), send: 'always' } + }); + { + const [serverRequest, response] = await Promise.all([ + server.waitForRequest('/empty.html'), + page.request.get(server.EMPTY_PAGE) + ]); + expect(serverRequest.headers.authorization).toBe('Basic ' + Buffer.from('user:pass').toString('base64')); + expect(response.status()).toBe(200); + } + { + const [serverRequest, response] = await Promise.all([ + server.waitForRequest('/empty.html'), + page.request.get(server.CROSS_PROCESS_PREFIX + '/empty.html') + ]); + // Not sent to another origin. + expect(serverRequest.headers.authorization).toBe(undefined); + expect(response.status()).toBe(200); + } + await page.close(); +}); + it('delete should support post data', async ({ context, server }) => { const [request, response] = await Promise.all([ server.waitForRequest('/simple.json'), diff --git a/tests/library/global-fetch.spec.ts b/tests/library/global-fetch.spec.ts index 58dec04519..a2eb629dc5 100644 --- a/tests/library/global-fetch.spec.ts +++ b/tests/library/global-fetch.spec.ts @@ -157,7 +157,7 @@ it('should support WWW-Authenticate: Basic', async ({ playwright, server }) => { it('should support HTTPCredentials.sendImmediately', async ({ playwright, server }) => { it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/30534' }); const request = await playwright.request.newContext({ - httpCredentials: { username: 'user', password: 'pass', origin: server.PREFIX.toUpperCase(), sendImmediately: true } + httpCredentials: { username: 'user', password: 'pass', origin: server.PREFIX.toUpperCase(), send: 'always' } }); { const [serverRequest, response] = await Promise.all([