From 6747844a33b6074a05b9c882c07457a48f1c0765 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Fri, 13 Sep 2024 14:44:31 +0200 Subject: [PATCH] chore: introduce option overrides on context/browser --- .../src/server/bidi/bidiChromium.ts | 3 ++- packages/playwright-core/src/server/browser.ts | 8 ++++++-- .../src/server/browserContext.ts | 7 ++----- .../playwright-core/src/server/browserType.ts | 17 ++++++++--------- .../src/server/chromium/chromium.ts | 3 ++- .../src/server/chromium/crBrowser.ts | 13 +++++++------ .../src/server/chromium/crPage.ts | 2 +- .../src/server/firefox/ffBrowser.ts | 12 +++++++----- .../socksClientCertificatesInterceptor.ts | 4 ++-- packages/playwright-core/src/server/types.ts | 10 +++++++++- .../playwright-core/src/server/webkit/webkit.ts | 3 ++- .../src/server/webkit/wkBrowser.ts | 13 +++++++------ tests/library/client-certificates.spec.ts | 1 + 13 files changed, 56 insertions(+), 40 deletions(-) diff --git a/packages/playwright-core/src/server/bidi/bidiChromium.ts b/packages/playwright-core/src/server/bidi/bidiChromium.ts index e94dabf072..32751bd51a 100644 --- a/packages/playwright-core/src/server/bidi/bidiChromium.ts +++ b/packages/playwright-core/src/server/bidi/bidiChromium.ts @@ -91,7 +91,7 @@ export class BidiChromium extends BrowserType { } private _innerDefaultArgs(options: types.LaunchOptions): string[] { - const { args = [], proxy } = options; + const { args = [] } = options; const userDataDirArg = args.find(arg => arg.startsWith('--user-data-dir')); if (userDataDirArg) throw this._createUserDataDirArgMisuseError('--user-data-dir'); @@ -125,6 +125,7 @@ export class BidiChromium extends BrowserType { } if (options.chromiumSandbox !== true) chromeArguments.push('--no-sandbox'); + const proxy = options.proxyOverride || options.proxy; if (proxy) { const proxyURL = new URL(proxy.server); const isSocks = proxyURL.protocol === 'socks5:'; diff --git a/packages/playwright-core/src/server/browser.ts b/packages/playwright-core/src/server/browser.ts index 663b9be377..faa1a16d3b 100644 --- a/packages/playwright-core/src/server/browser.ts +++ b/packages/playwright-core/src/server/browser.ts @@ -74,15 +74,19 @@ export abstract class Browser extends SdkObject { this.instrumentation.onBrowserOpen(this); } - abstract doCreateNewContext(options: channels.BrowserNewContextParams): Promise; + abstract doCreateNewContext(options: types.BrowserContextOptions): Promise; abstract contexts(): BrowserContext[]; abstract isConnected(): boolean; abstract version(): string; abstract userAgent(): string; - async newContext(metadata: CallMetadata, options: channels.BrowserNewContextParams): Promise { + async newContext(metadata: CallMetadata, options: types.BrowserContextOptions): Promise { validateBrowserContextOptions(options, this.options); const clientCertificatesProxy = await createClientCertificatesProxyIfNeeded(options, this.options); + if (clientCertificatesProxy) { + options.proxyOverride = await clientCertificatesProxy.listen(); + options.ignoreHTTPSErrorsOverride = true; + } let context; try { context = await this.doCreateNewContext(options); diff --git a/packages/playwright-core/src/server/browserContext.ts b/packages/playwright-core/src/server/browserContext.ts index 8ddbe68f89..75593f9f22 100644 --- a/packages/playwright-core/src/server/browserContext.ts +++ b/packages/playwright-core/src/server/browserContext.ts @@ -68,7 +68,7 @@ export abstract class BrowserContext extends SdkObject { readonly _timeoutSettings = new TimeoutSettings(); readonly _pageBindings = new Map(); readonly _activeProgressControllers = new Set(); - readonly _options: channels.BrowserNewContextParams; + readonly _options: types.BrowserContextOptions; _requestInterceptor?: network.RouteHandler; private _isPersistentContext: boolean; private _closedStatus: 'open' | 'closing' | 'closed' = 'open'; @@ -665,10 +665,7 @@ export async function createClientCertificatesProxyIfNeeded(options: channels.Br if ((options.proxy?.server && options.proxy?.server !== 'per-context') || (browserOptions?.proxy?.server && browserOptions?.proxy?.server !== 'http://per-context')) throw new Error('Cannot specify both proxy and clientCertificates'); verifyClientCertificates(options.clientCertificates); - const clientCertificatesProxy = new ClientCertificatesProxy(options); - options.proxy = { server: await clientCertificatesProxy.listen() }; - options.ignoreHTTPSErrors = true; - return clientCertificatesProxy; + return new ClientCertificatesProxy(options); } export function validateBrowserContextOptions(options: channels.BrowserNewContextParams, browserOptions: BrowserOptions) { diff --git a/packages/playwright-core/src/server/browserType.ts b/packages/playwright-core/src/server/browserType.ts index d0c3174a59..8e7c9cbd4d 100644 --- a/packages/playwright-core/src/server/browserType.ts +++ b/packages/playwright-core/src/server/browserType.ts @@ -92,23 +92,22 @@ export abstract class BrowserType extends SdkObject { return browser; } - async launchPersistentContext(metadata: CallMetadata, userDataDir: string, options: channels.BrowserTypeLaunchPersistentContextOptions & { useWebSocket?: boolean }): Promise { - options = this._validateLaunchOptions(options); + async launchPersistentContext(metadata: CallMetadata, userDataDir: string, persistentContextOptions: channels.BrowserTypeLaunchPersistentContextOptions & { useWebSocket?: boolean }): Promise { + const launchOptions = this._validateLaunchOptions(persistentContextOptions); if (this._useBidi) - options.useWebSocket = true; + launchOptions.useWebSocket = true; const controller = new ProgressController(metadata, this); - const persistent: channels.BrowserNewContextParams = { ...options }; controller.setLogName('browser'); const browser = await controller.run(async progress => { // Note: Any initial TLS requests will fail since we rely on the Page/Frames initialize which sets ignoreHTTPSErrors. - const clientCertificatesProxy = await createClientCertificatesProxyIfNeeded(persistent); + const clientCertificatesProxy = await createClientCertificatesProxyIfNeeded(persistentContextOptions); if (clientCertificatesProxy) - options.proxy = persistent.proxy; + launchOptions.proxyOverride = await clientCertificatesProxy?.listen(); progress.cleanupWhenAborted(() => clientCertificatesProxy?.close()); - const browser = await this._innerLaunchWithRetries(progress, options, persistent, helper.debugProtocolLogger(), userDataDir).catch(e => { throw this._rewriteStartupLog(e); }); + const browser = await this._innerLaunchWithRetries(progress, launchOptions, persistentContextOptions, helper.debugProtocolLogger(), userDataDir).catch(e => { throw this._rewriteStartupLog(e); }); browser._defaultContext!._clientCertificatesProxy = clientCertificatesProxy; return browser; - }, TimeoutSettings.launchTimeout(options)); + }, TimeoutSettings.launchTimeout(launchOptions)); return browser._defaultContext!; } @@ -289,7 +288,7 @@ export abstract class BrowserType extends SdkObject { throw new Error('Connecting to SELENIUM_REMOTE_URL is only supported by Chromium'); } - private _validateLaunchOptions(options: Options): Options { + private _validateLaunchOptions(options: Options): types.LaunchOptions { const { devtools = false } = options; let { headless = !devtools, downloadsPath, proxy } = options; if (debugMode()) diff --git a/packages/playwright-core/src/server/chromium/chromium.ts b/packages/playwright-core/src/server/chromium/chromium.ts index d84ae3952e..625ee75fc7 100644 --- a/packages/playwright-core/src/server/chromium/chromium.ts +++ b/packages/playwright-core/src/server/chromium/chromium.ts @@ -287,7 +287,7 @@ export class Chromium extends BrowserType { } private _innerDefaultArgs(options: types.LaunchOptions): string[] { - const { args = [], proxy } = options; + const { args = [] } = options; const userDataDirArg = args.find(arg => arg.startsWith('--user-data-dir')); if (userDataDirArg) throw this._createUserDataDirArgMisuseError('--user-data-dir'); @@ -321,6 +321,7 @@ export class Chromium extends BrowserType { } if (options.chromiumSandbox !== true) chromeArguments.push('--no-sandbox'); + const proxy = options.proxyOverride || options.proxy; if (proxy) { const proxyURL = new URL(proxy.server); const isSocks = proxyURL.protocol === 'socks5:'; diff --git a/packages/playwright-core/src/server/chromium/crBrowser.ts b/packages/playwright-core/src/server/chromium/crBrowser.ts index 42b916c186..e0409c1b16 100644 --- a/packages/playwright-core/src/server/chromium/crBrowser.ts +++ b/packages/playwright-core/src/server/chromium/crBrowser.ts @@ -100,18 +100,19 @@ export class CRBrowser extends Browser { this._session.on('Browser.downloadProgress', this._onDownloadProgress.bind(this)); } - async doCreateNewContext(options: channels.BrowserNewContextParams): Promise { + async doCreateNewContext(options: types.BrowserContextOptions): Promise { + const proxy = options.proxyOverride || options.proxy; let proxyBypassList = undefined; - if (options.proxy) { + if (proxy) { if (process.env.PLAYWRIGHT_DISABLE_FORCED_CHROMIUM_PROXIED_LOOPBACK) - proxyBypassList = options.proxy.bypass; + proxyBypassList = proxy.bypass; else - proxyBypassList = '<-loopback>' + (options.proxy.bypass ? `,${options.proxy.bypass}` : ''); + proxyBypassList = '<-loopback>' + (proxy.bypass ? `,${proxy.bypass}` : ''); } const { browserContextId } = await this._session.send('Target.createBrowserContext', { disposeOnDetach: true, - proxyServer: options.proxy ? options.proxy.server : undefined, + proxyServer: proxy ? proxy.server : undefined, proxyBypassList, }); const context = new CRBrowserContext(this, browserContextId, options); @@ -340,7 +341,7 @@ export class CRBrowserContext extends BrowserContext { declare readonly _browser: CRBrowser; - constructor(browser: CRBrowser, browserContextId: string | undefined, options: channels.BrowserNewContextParams) { + constructor(browser: CRBrowser, browserContextId: string | undefined, options: types.BrowserContextOptions) { super(browser, options, browserContextId); this._authenticateProxyViaCredentials(); } diff --git a/packages/playwright-core/src/server/chromium/crPage.ts b/packages/playwright-core/src/server/chromium/crPage.ts index 002dfa09be..82c54c9d0c 100644 --- a/packages/playwright-core/src/server/chromium/crPage.ts +++ b/packages/playwright-core/src/server/chromium/crPage.ts @@ -543,7 +543,7 @@ class FrameSession { const options = this._crPage._browserContext._options; if (options.bypassCSP) promises.push(this._client.send('Page.setBypassCSP', { enabled: true })); - if (options.ignoreHTTPSErrors) + if (options.ignoreHTTPSErrors || options.ignoreHTTPSErrorsOverride) promises.push(this._client.send('Security.setIgnoreCertificateErrors', { ignore: true })); if (this._isMainFrame()) promises.push(this._updateViewport()); diff --git a/packages/playwright-core/src/server/firefox/ffBrowser.ts b/packages/playwright-core/src/server/firefox/ffBrowser.ts index 94b90bbcea..eed3d6beb7 100644 --- a/packages/playwright-core/src/server/firefox/ffBrowser.ts +++ b/packages/playwright-core/src/server/firefox/ffBrowser.ts @@ -58,8 +58,9 @@ export class FFBrowser extends Browser { browser._defaultContext = new FFBrowserContext(browser, undefined, options.persistent); promises.push((browser._defaultContext as FFBrowserContext)._initialize()); } - if (options.proxy) - promises.push(browser.session.send('Browser.setBrowserProxy', toJugglerProxyOptions(options.proxy))); + const proxy = options.originalLaunchOptions.proxyOverride || options.proxy; + if (proxy) + promises.push(browser.session.send('Browser.setBrowserProxy', toJugglerProxyOptions(proxy))); await Promise.all(promises); return browser; } @@ -205,7 +206,7 @@ export class FFBrowserContext extends BrowserContext { promises.push(this._browser.session.send('Browser.setUserAgentOverride', { browserContextId, userAgent: this._options.userAgent })); if (this._options.bypassCSP) promises.push(this._browser.session.send('Browser.setBypassCSP', { browserContextId, bypassCSP: true })); - if (this._options.ignoreHTTPSErrors) + if (this._options.ignoreHTTPSErrors || this._options.ignoreHTTPSErrorsOverride) promises.push(this._browser.session.send('Browser.setIgnoreHTTPSErrors', { browserContextId, ignoreHTTPSErrors: true })); if (this._options.javaScriptEnabled === false) promises.push(this._browser.session.send('Browser.setJavaScriptDisabled', { browserContextId, javaScriptDisabled: true })); @@ -251,10 +252,11 @@ export class FFBrowserContext extends BrowserContext { }); })); } - if (this._options.proxy) { + const proxy = this._options.proxyOverride || this._options.proxy; + if (proxy) { promises.push(this._browser.session.send('Browser.setContextProxy', { browserContextId: this._browserContextId, - ...toJugglerProxyOptions(this._options.proxy) + ...toJugglerProxyOptions(proxy) })); } diff --git a/packages/playwright-core/src/server/socksClientCertificatesInterceptor.ts b/packages/playwright-core/src/server/socksClientCertificatesInterceptor.ts index 2dd900bf89..3f279262b9 100644 --- a/packages/playwright-core/src/server/socksClientCertificatesInterceptor.ts +++ b/packages/playwright-core/src/server/socksClientCertificatesInterceptor.ts @@ -282,9 +282,9 @@ export class ClientCertificatesProxy { } } - public async listen(): Promise { + public async listen() { const port = await this._socksProxy.listen(0, '127.0.0.1'); - return `socks5://127.0.0.1:${port}`; + return { server: `socks5://127.0.0.1:${port}` }; } public async close() { diff --git a/packages/playwright-core/src/server/types.ts b/packages/playwright-core/src/server/types.ts index 226216c397..8daadc7e8a 100644 --- a/packages/playwright-core/src/server/types.ts +++ b/packages/playwright-core/src/server/types.ts @@ -150,7 +150,15 @@ export type NormalizedContinueOverrides = { export type EmulatedSize = { viewport: Size, screen: Size }; -export type LaunchOptions = channels.BrowserTypeLaunchOptions & { useWebSocket?: boolean }; +export type LaunchOptions = channels.BrowserTypeLaunchOptions & { + useWebSocket?: boolean, + proxyOverride?: ProxySettings, +}; + +export type BrowserContextOptions = channels.BrowserNewContextOptions & { + proxyOverride?: ProxySettings; + ignoreHTTPSErrorsOverride?: boolean; +}; export type ProtocolLogger = (direction: 'send' | 'receive', message: object) => void; diff --git a/packages/playwright-core/src/server/webkit/webkit.ts b/packages/playwright-core/src/server/webkit/webkit.ts index b25b62a421..9a11f6c56d 100644 --- a/packages/playwright-core/src/server/webkit/webkit.ts +++ b/packages/playwright-core/src/server/webkit/webkit.ts @@ -53,7 +53,7 @@ export class WebKit extends BrowserType { } override defaultArgs(options: types.LaunchOptions, isPersistent: boolean, userDataDir: string): string[] { - const { args = [], proxy, headless } = options; + const { args = [], headless } = options; const userDataDirArg = args.find(arg => arg.startsWith('--user-data-dir')); if (userDataDirArg) throw this._createUserDataDirArgMisuseError('--user-data-dir'); @@ -68,6 +68,7 @@ export class WebKit extends BrowserType { webkitArguments.push(`--user-data-dir=${userDataDir}`); else webkitArguments.push(`--no-startup-window`); + const proxy = options.proxyOverride || options.proxy; if (proxy) { if (process.platform === 'darwin') { webkitArguments.push(`--proxy=${proxy.server}`); diff --git a/packages/playwright-core/src/server/webkit/wkBrowser.ts b/packages/playwright-core/src/server/webkit/wkBrowser.ts index 92231edd75..f0c337266a 100644 --- a/packages/playwright-core/src/server/webkit/wkBrowser.ts +++ b/packages/playwright-core/src/server/webkit/wkBrowser.ts @@ -81,12 +81,13 @@ export class WKBrowser extends Browser { this._didClose(); } - async doCreateNewContext(options: channels.BrowserNewContextParams): Promise { - const createOptions = options.proxy ? { - // Enable socks5 hostname resolution on Windows. Workaround can be removed once fixed upstream. + async doCreateNewContext(options: types.BrowserContextOptions): Promise { + const proxy = options.proxyOverride || options.proxy; + const createOptions = proxy ? { + // Enable socks5 hostname resolution on Windows. // See https://github.com/microsoft/playwright/issues/20451 - proxyServer: process.platform === 'win32' ? options.proxy.server.replace(/^socks5:\/\//, 'socks5h://') : options.proxy.server, - proxyBypassList: options.proxy.bypass + proxyServer: process.platform === 'win32' ? proxy.server.replace(/^socks5:\/\//, 'socks5h://') : proxy.server, + proxyBypassList: proxy.bypass } : undefined; const { browserContextId } = await this._browserSession.send('Playwright.createContext', createOptions); options.userAgent = options.userAgent || DEFAULT_USER_AGENT; @@ -221,7 +222,7 @@ export class WKBrowserContext extends BrowserContext { downloadPath: this._browser.options.downloadsPath, browserContextId })); - if (this._options.ignoreHTTPSErrors) + if (this._options.ignoreHTTPSErrors || this._options.ignoreHTTPSErrorsOverride) promises.push(this._browser._browserSession.send('Playwright.setIgnoreCertificateErrors', { browserContextId, ignore: true })); if (this._options.locale) promises.push(this._browser._browserSession.send('Playwright.setLanguages', { browserContextId, languages: [this._options.locale] })); diff --git a/tests/library/client-certificates.spec.ts b/tests/library/client-certificates.spec.ts index 682df0b00f..899b9819be 100644 --- a/tests/library/client-certificates.spec.ts +++ b/tests/library/client-certificates.spec.ts @@ -546,6 +546,7 @@ test.describe('browser', () => { keyPath: asset('client-certificates/client/trusted/key.pem'), }; const page = await browser.newPage({ + ignoreHTTPSErrors: true, clientCertificates: [{ origin: new URL(serverURL).origin, ...baseOptions,