This commit is contained in:
Max Schmitt 2024-07-12 10:17:58 +02:00
parent 30506afeba
commit eeef44455f
5 changed files with 59 additions and 43 deletions

View file

@ -84,7 +84,7 @@ export abstract class Browser extends SdkObject {
async newContext(metadata: CallMetadata, options: channels.BrowserNewContextParams): Promise<BrowserContext> { async newContext(metadata: CallMetadata, options: channels.BrowserNewContextParams): Promise<BrowserContext> {
validateBrowserContextOptions(options, this.options); validateBrowserContextOptions(options, this.options);
const clientCertificatesProxy = await createClientCertificatesProxyIfNeeded(options); const clientCertificatesProxy = await createClientCertificatesProxyIfNeeded(options, this.options);
const context = await this.doCreateNewContext(options); const context = await this.doCreateNewContext(options);
context._clientCertificatesProxy = clientCertificatesProxy; context._clientCertificatesProxy = clientCertificatesProxy;
if (options.storageState) if (options.storageState)

View file

@ -654,10 +654,10 @@ export function assertBrowserContextIsNotOwned(context: BrowserContext) {
} }
} }
export async function createClientCertificatesProxyIfNeeded(options: channels.BrowserNewContextOptions) { export async function createClientCertificatesProxyIfNeeded(options: channels.BrowserNewContextOptions, browserOptions?: BrowserOptions) {
if (!options.clientCertificates?.length) if (!options.clientCertificates?.length)
return; return;
if (options.proxy?.server) if (options.proxy?.server || browserOptions?.proxy?.server)
throw new Error('Cannot specify both proxy and clientCertificates'); throw new Error('Cannot specify both proxy and clientCertificates');
const clientCertificatesProxy = new ClientCertificatesProxy(options); const clientCertificatesProxy = new ClientCertificatesProxy(options);
options.proxy = { server: await clientCertificatesProxy.listen() }; options.proxy = { server: await clientCertificatesProxy.listen() };
@ -699,12 +699,8 @@ export function validateBrowserContextOptions(options: channels.BrowserNewContex
if (options.proxy) { if (options.proxy) {
if (!browserOptions.proxy && browserOptions.isChromium && os.platform() === 'win32') if (!browserOptions.proxy && browserOptions.isChromium && os.platform() === 'win32')
throw new Error(`Browser needs to be launched with the global proxy. If all contexts override the proxy, global proxy will be never used and can be any string, for example "launch({ proxy: { server: 'http://per-context' } })"`); throw new Error(`Browser needs to be launched with the global proxy. If all contexts override the proxy, global proxy will be never used and can be any string, for example "launch({ proxy: { server: 'http://per-context' } })"`);
if (!browserOptions.persistent && options.clientCertificates)
throw new Error('Cannot specify both proxy and clientCertificates');
options.proxy = normalizeProxySettings(options.proxy); options.proxy = normalizeProxySettings(options.proxy);
} }
if (!browserOptions.persistent && browserOptions.proxy?.server && options.clientCertificates)
throw new Error('Cannot specify both proxy and clientCertificates');
verifyGeolocation(options.geolocation); verifyGeolocation(options.geolocation);
verifyClientCertificates(options.clientCertificates); verifyClientCertificates(options.clientCertificates);
} }

View file

@ -40,6 +40,7 @@ import type { CallMetadata } from './instrumentation';
import { SdkObject } from './instrumentation'; import { SdkObject } from './instrumentation';
import { ManualPromise } from '../utils/manualPromise'; import { ManualPromise } from '../utils/manualPromise';
import { type ProtocolError, isProtocolError } from './protocolError'; import { type ProtocolError, isProtocolError } from './protocolError';
import type { ClientCertificatesProxy } from './socksClientCertificatesInterceptor';
export const kNoXServerRunningError = 'Looks like you launched a headed browser without having a XServer running.\n' + export const kNoXServerRunningError = 'Looks like you launched a headed browser without having a XServer running.\n' +
'Set either \'headless: true\' or use \'xvfb-run <your-playwright-app>\' before running Playwright.\n\n<3 Playwright Team'; 'Set either \'headless: true\' or use \'xvfb-run <your-playwright-app>\' before running Playwright.\n\n<3 Playwright Team';
@ -77,7 +78,7 @@ export abstract class BrowserType extends SdkObject {
async launchPersistentContext(metadata: CallMetadata, userDataDir: string, options: channels.BrowserTypeLaunchPersistentContextOptions & { useWebSocket?: boolean }): Promise<BrowserContext> { async launchPersistentContext(metadata: CallMetadata, userDataDir: string, options: channels.BrowserTypeLaunchPersistentContextOptions & { useWebSocket?: boolean }): Promise<BrowserContext> {
options = this._validateLaunchOptions(options); options = this._validateLaunchOptions(options);
const controller = new ProgressController(metadata, this); const controller = new ProgressController(metadata, this);
const persistent: channels.BrowserNewContextParams = options; const persistent: channels.BrowserNewContextParams = { ...options };
controller.setLogName('browser'); controller.setLogName('browser');
const browser = await controller.run(progress => { const browser = await controller.run(progress => {
return this._innerLaunchWithRetries(progress, options, persistent, helper.debugProtocolLogger(), userDataDir).catch(e => { throw this._rewriteStartupLog(e); }); return this._innerLaunchWithRetries(progress, options, persistent, helper.debugProtocolLogger(), userDataDir).catch(e => { throw this._rewriteStartupLog(e); });
@ -101,9 +102,22 @@ export abstract class BrowserType extends SdkObject {
async _innerLaunch(progress: Progress, options: types.LaunchOptions, persistent: channels.BrowserNewContextParams | undefined, protocolLogger: types.ProtocolLogger, maybeUserDataDir?: string): Promise<Browser> { async _innerLaunch(progress: Progress, options: types.LaunchOptions, persistent: channels.BrowserNewContextParams | undefined, protocolLogger: types.ProtocolLogger, maybeUserDataDir?: string): Promise<Browser> {
options.proxy = options.proxy ? normalizeProxySettings(options.proxy) : undefined; options.proxy = options.proxy ? normalizeProxySettings(options.proxy) : undefined;
const clientCertificatesProxy = await createClientCertificatesProxyIfNeeded(persistent ?? {}); let clientCertificatesProxy: ClientCertificatesProxy | undefined;
if (persistent) {
// Note: Any initial requests done by browser will not be intercepted by the client certificates proxy.
// We rely when the Page/Frames to ignoreHTTPSErrors to bypass the certificate errors.
clientCertificatesProxy = await createClientCertificatesProxyIfNeeded(persistent);
options.proxy = persistent.proxy;
}
const browserLogsCollector = new RecentLogsCollector(); const browserLogsCollector = new RecentLogsCollector();
const { browserProcess, userDataDir, artifactsDir, transport } = await this._launchProcess(progress, options, !!persistent, browserLogsCollector, maybeUserDataDir); let launchInfo;
try {
launchInfo = await this._launchProcess(progress, options, !!persistent, browserLogsCollector, maybeUserDataDir);
} catch (error) {
await clientCertificatesProxy?.close();
throw error;
}
const { browserProcess, userDataDir, artifactsDir, transport } = launchInfo;
if ((options as any).__testHookBeforeCreateBrowser) if ((options as any).__testHookBeforeCreateBrowser)
await (options as any).__testHookBeforeCreateBrowser(); await (options as any).__testHookBeforeCreateBrowser();
const browserOptions: BrowserOptions = { const browserOptions: BrowserOptions = {
@ -127,13 +141,19 @@ export abstract class BrowserType extends SdkObject {
if (persistent) if (persistent)
validateBrowserContextOptions(persistent, browserOptions); validateBrowserContextOptions(persistent, browserOptions);
copyTestHooks(options, browserOptions); copyTestHooks(options, browserOptions);
const browser = await this._connectToTransport(transport, browserOptions); let browser;
try {
browser = await this._connectToTransport(transport, browserOptions);
} catch (error) {
await clientCertificatesProxy?.close();
throw error;
}
(browser as any)._userDataDirForTest = userDataDir; (browser as any)._userDataDirForTest = userDataDir;
if (persistent)
browser._defaultContext!._clientCertificatesProxy = clientCertificatesProxy;
// We assume no control when using custom arguments, and do not prepare the default context in that case. // We assume no control when using custom arguments, and do not prepare the default context in that case.
if (persistent && !options.ignoreAllDefaultArgs) if (persistent && !options.ignoreAllDefaultArgs)
await browser._defaultContext!._loadDefaultContext(progress); await browser._defaultContext!._loadDefaultContext(progress);
if (persistent)
browser._defaultContext!._clientCertificatesProxy = clientCertificatesProxy;
return browser; return browser;
} }

View file

@ -13222,15 +13222,15 @@ export interface BrowserType<Unused = {}> {
chromiumSandbox?: boolean; chromiumSandbox?: boolean;
/** /**
* An array of client certificates to be used. Each certificate object must have `cert` and `key` or `pfx` to load the * An array of client certificates to be used. Each certificate object must have both `certPath` and `keyPath` or a
* client certificate. Optionally, `passphrase` property should be provided if the private key is encrypted. If the * single `pfxPath` to load the client certificate. Optionally, `passphrase` property should be provided if the
* certificate is valid only for specific URLs, the `url` property should be provided with a glob pattern to match the * private key is encrypted. If the certificate is valid only for specific URLs, the `url` property should be provided
* URLs that the certificate is valid for. * with a glob pattern to match the URLs that the certificate is valid for.
* *
* **NOTE** Using Client Certificates in combination with Proxy Servers is not supported. * **NOTE** Using Client Certificates in combination with Proxy Servers is not supported.
* *
* **NOTE** When using WebKit on macOS, accessing `localhost` might not work as expected. Instead, use * **NOTE** When using WebKit on macOS, accessing `localhost` will not pick up client certificates. You can make it
* `playwright.local` as the hostname. * work by replacing `localhost` with `local.playwright`. Instead, use `playwright.local` as the hostname.
*/ */
clientCertificates?: Array<{ clientCertificates?: Array<{
/** /**
@ -15634,15 +15634,15 @@ export interface APIRequest {
baseURL?: string; baseURL?: string;
/** /**
* An array of client certificates to be used. Each certificate object must have `cert` and `key` or `pfx` to load the * An array of client certificates to be used. Each certificate object must have both `certPath` and `keyPath` or a
* client certificate. Optionally, `passphrase` property should be provided if the private key is encrypted. If the * single `pfxPath` to load the client certificate. Optionally, `passphrase` property should be provided if the
* certificate is valid only for specific URLs, the `url` property should be provided with a glob pattern to match the * private key is encrypted. If the certificate is valid only for specific URLs, the `url` property should be provided
* URLs that the certificate is valid for. * with a glob pattern to match the URLs that the certificate is valid for.
* *
* **NOTE** Using Client Certificates in combination with Proxy Servers is not supported. * **NOTE** Using Client Certificates in combination with Proxy Servers is not supported.
* *
* **NOTE** When using WebKit on macOS, accessing `localhost` might not work as expected. Instead, use * **NOTE** When using WebKit on macOS, accessing `localhost` will not pick up client certificates. You can make it
* `playwright.local` as the hostname. * work by replacing `localhost` with `local.playwright`. Instead, use `playwright.local` as the hostname.
*/ */
clientCertificates?: Array<{ clientCertificates?: Array<{
/** /**
@ -16828,15 +16828,15 @@ export interface Browser extends EventEmitter {
bypassCSP?: boolean; bypassCSP?: boolean;
/** /**
* An array of client certificates to be used. Each certificate object must have `cert` and `key` or `pfx` to load the * An array of client certificates to be used. Each certificate object must have both `certPath` and `keyPath` or a
* client certificate. Optionally, `passphrase` property should be provided if the private key is encrypted. If the * single `pfxPath` to load the client certificate. Optionally, `passphrase` property should be provided if the
* certificate is valid only for specific URLs, the `url` property should be provided with a glob pattern to match the * private key is encrypted. If the certificate is valid only for specific URLs, the `url` property should be provided
* URLs that the certificate is valid for. * with a glob pattern to match the URLs that the certificate is valid for.
* *
* **NOTE** Using Client Certificates in combination with Proxy Servers is not supported. * **NOTE** Using Client Certificates in combination with Proxy Servers is not supported.
* *
* **NOTE** When using WebKit on macOS, accessing `localhost` might not work as expected. Instead, use * **NOTE** When using WebKit on macOS, accessing `localhost` will not pick up client certificates. You can make it
* `playwright.local` as the hostname. * work by replacing `localhost` with `local.playwright`. Instead, use `playwright.local` as the hostname.
*/ */
clientCertificates?: Array<{ clientCertificates?: Array<{
/** /**
@ -20303,15 +20303,15 @@ export interface BrowserContextOptions {
bypassCSP?: boolean; bypassCSP?: boolean;
/** /**
* An array of client certificates to be used. Each certificate object must have `cert` and `key` or `pfx` to load the * An array of client certificates to be used. Each certificate object must have both `certPath` and `keyPath` or a
* client certificate. Optionally, `passphrase` property should be provided if the private key is encrypted. If the * single `pfxPath` to load the client certificate. Optionally, `passphrase` property should be provided if the
* certificate is valid only for specific URLs, the `url` property should be provided with a glob pattern to match the * private key is encrypted. If the certificate is valid only for specific URLs, the `url` property should be provided
* URLs that the certificate is valid for. * with a glob pattern to match the URLs that the certificate is valid for.
* *
* **NOTE** Using Client Certificates in combination with Proxy Servers is not supported. * **NOTE** Using Client Certificates in combination with Proxy Servers is not supported.
* *
* **NOTE** When using WebKit on macOS, accessing `localhost` might not work as expected. Instead, use * **NOTE** When using WebKit on macOS, accessing `localhost` will not pick up client certificates. You can make it
* `playwright.local` as the hostname. * work by replacing `localhost` with `local.playwright`. Instead, use `playwright.local` as the hostname.
*/ */
clientCertificates?: Array<{ clientCertificates?: Array<{
/** /**

View file

@ -5202,15 +5202,15 @@ export interface PlaywrightTestOptions {
*/ */
colorScheme: ColorScheme; colorScheme: ColorScheme;
/** /**
* An array of client certificates to be used. Each certificate object must have `cert` and `key` or `pfx` to load the * An array of client certificates to be used. Each certificate object must have both `certPath` and `keyPath` or a
* client certificate. Optionally, `passphrase` property should be provided if the private key is encrypted. If the * single `pfxPath` to load the client certificate. Optionally, `passphrase` property should be provided if the
* certificate is valid only for specific URLs, the `url` property should be provided with a glob pattern to match the * private key is encrypted. If the certificate is valid only for specific URLs, the `url` property should be provided
* URLs that the certificate is valid for. * with a glob pattern to match the URLs that the certificate is valid for.
* *
* **NOTE** Using Client Certificates in combination with Proxy Servers is not supported. * **NOTE** Using Client Certificates in combination with Proxy Servers is not supported.
* *
* **NOTE** When using WebKit on macOS, accessing `localhost` might not work as expected. Instead, use * **NOTE** When using WebKit on macOS, accessing `localhost` will not pick up client certificates. You can make it
* `playwright.local` as the hostname. * work by replacing `localhost` with `local.playwright`. Instead, use `playwright.local` as the hostname.
* *
* **Usage** * **Usage**
* *