chore: introduce option overrides on context/browser (#32606)
This commit is contained in:
parent
9bb1c86f93
commit
79cba7d704
|
|
@ -29,6 +29,7 @@ import { validateBrowserContextOptions } from '../browserContext';
|
||||||
import { ProgressController } from '../progress';
|
import { ProgressController } from '../progress';
|
||||||
import { CRBrowser } from '../chromium/crBrowser';
|
import { CRBrowser } from '../chromium/crBrowser';
|
||||||
import { helper } from '../helper';
|
import { helper } from '../helper';
|
||||||
|
import type * as types from '../types';
|
||||||
import { PipeTransport } from '../../protocol/transport';
|
import { PipeTransport } from '../../protocol/transport';
|
||||||
import { RecentLogsCollector } from '../../utils/debugLogger';
|
import { RecentLogsCollector } from '../../utils/debugLogger';
|
||||||
import { gracefullyCloseSet } from '../../utils/processLauncher';
|
import { gracefullyCloseSet } from '../../utils/processLauncher';
|
||||||
|
|
@ -309,7 +310,7 @@ export class AndroidDevice extends SdkObject {
|
||||||
return await this._connectToBrowser(socketName);
|
return await this._connectToBrowser(socketName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _connectToBrowser(socketName: string, options: channels.BrowserNewContextParams = {}): Promise<BrowserContext> {
|
private async _connectToBrowser(socketName: string, options: types.BrowserContextOptions = {}): Promise<BrowserContext> {
|
||||||
const socket = await this._waitForLocalAbstract(socketName);
|
const socket = await this._waitForLocalAbstract(socketName);
|
||||||
const androidBrowser = new AndroidBrowser(this, socket);
|
const androidBrowser = new AndroidBrowser(this, socket);
|
||||||
await androidBrowser._init();
|
await androidBrowser._init();
|
||||||
|
|
|
||||||
|
|
@ -111,7 +111,7 @@ export class BidiBrowser extends Browser {
|
||||||
this._didClose();
|
this._didClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
async doCreateNewContext(options: channels.BrowserNewContextParams): Promise<BrowserContext> {
|
async doCreateNewContext(options: types.BrowserContextOptions): Promise<BrowserContext> {
|
||||||
const { userContext } = await this._browserSession.send('browser.createUserContext', {});
|
const { userContext } = await this._browserSession.send('browser.createUserContext', {});
|
||||||
const context = new BidiBrowserContext(this, userContext, options);
|
const context = new BidiBrowserContext(this, userContext, options);
|
||||||
await context._initialize();
|
await context._initialize();
|
||||||
|
|
@ -190,7 +190,7 @@ export class BidiBrowser extends Browser {
|
||||||
export class BidiBrowserContext extends BrowserContext {
|
export class BidiBrowserContext extends BrowserContext {
|
||||||
declare readonly _browser: BidiBrowser;
|
declare readonly _browser: BidiBrowser;
|
||||||
|
|
||||||
constructor(browser: BidiBrowser, browserContextId: string | undefined, options: channels.BrowserNewContextParams) {
|
constructor(browser: BidiBrowser, browserContextId: string | undefined, options: types.BrowserContextOptions) {
|
||||||
super(browser, options, browserContextId);
|
super(browser, options, browserContextId);
|
||||||
this._authenticateProxyViaHeader();
|
this._authenticateProxyViaHeader();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,7 @@ export class BidiChromium extends BrowserType {
|
||||||
}
|
}
|
||||||
|
|
||||||
private _innerDefaultArgs(options: types.LaunchOptions): string[] {
|
private _innerDefaultArgs(options: types.LaunchOptions): string[] {
|
||||||
const { args = [], proxy } = options;
|
const { args = [] } = options;
|
||||||
const userDataDirArg = args.find(arg => arg.startsWith('--user-data-dir'));
|
const userDataDirArg = args.find(arg => arg.startsWith('--user-data-dir'));
|
||||||
if (userDataDirArg)
|
if (userDataDirArg)
|
||||||
throw this._createUserDataDirArgMisuseError('--user-data-dir');
|
throw this._createUserDataDirArgMisuseError('--user-data-dir');
|
||||||
|
|
@ -125,6 +125,7 @@ export class BidiChromium extends BrowserType {
|
||||||
}
|
}
|
||||||
if (options.chromiumSandbox !== true)
|
if (options.chromiumSandbox !== true)
|
||||||
chromeArguments.push('--no-sandbox');
|
chromeArguments.push('--no-sandbox');
|
||||||
|
const proxy = options.proxyOverride || options.proxy;
|
||||||
if (proxy) {
|
if (proxy) {
|
||||||
const proxyURL = new URL(proxy.server);
|
const proxyURL = new URL(proxy.server);
|
||||||
const isSocks = proxyURL.protocol === 'socks5:';
|
const isSocks = proxyURL.protocol === 'socks5:';
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ export type BrowserOptions = {
|
||||||
downloadsPath: string,
|
downloadsPath: string,
|
||||||
tracesDir: string,
|
tracesDir: string,
|
||||||
headful?: boolean,
|
headful?: boolean,
|
||||||
persistent?: channels.BrowserNewContextParams, // Undefined means no persistent context.
|
persistent?: types.BrowserContextOptions, // Undefined means no persistent context.
|
||||||
browserProcess: BrowserProcess,
|
browserProcess: BrowserProcess,
|
||||||
customExecutablePath?: string;
|
customExecutablePath?: string;
|
||||||
proxy?: ProxySettings,
|
proxy?: ProxySettings,
|
||||||
|
|
@ -74,15 +74,19 @@ export abstract class Browser extends SdkObject {
|
||||||
this.instrumentation.onBrowserOpen(this);
|
this.instrumentation.onBrowserOpen(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract doCreateNewContext(options: channels.BrowserNewContextParams): Promise<BrowserContext>;
|
abstract doCreateNewContext(options: types.BrowserContextOptions): Promise<BrowserContext>;
|
||||||
abstract contexts(): BrowserContext[];
|
abstract contexts(): BrowserContext[];
|
||||||
abstract isConnected(): boolean;
|
abstract isConnected(): boolean;
|
||||||
abstract version(): string;
|
abstract version(): string;
|
||||||
abstract userAgent(): string;
|
abstract userAgent(): string;
|
||||||
|
|
||||||
async newContext(metadata: CallMetadata, options: channels.BrowserNewContextParams): Promise<BrowserContext> {
|
async newContext(metadata: CallMetadata, options: types.BrowserContextOptions): Promise<BrowserContext> {
|
||||||
validateBrowserContextOptions(options, this.options);
|
validateBrowserContextOptions(options, this.options);
|
||||||
const clientCertificatesProxy = await createClientCertificatesProxyIfNeeded(options, this.options);
|
const clientCertificatesProxy = await createClientCertificatesProxyIfNeeded(options, this.options);
|
||||||
|
if (clientCertificatesProxy) {
|
||||||
|
options.proxyOverride = await clientCertificatesProxy.listen();
|
||||||
|
options.internalIgnoreHTTPSErrors = true;
|
||||||
|
}
|
||||||
let context;
|
let context;
|
||||||
try {
|
try {
|
||||||
context = await this.doCreateNewContext(options);
|
context = await this.doCreateNewContext(options);
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@ export abstract class BrowserContext extends SdkObject {
|
||||||
readonly _timeoutSettings = new TimeoutSettings();
|
readonly _timeoutSettings = new TimeoutSettings();
|
||||||
readonly _pageBindings = new Map<string, PageBinding>();
|
readonly _pageBindings = new Map<string, PageBinding>();
|
||||||
readonly _activeProgressControllers = new Set<ProgressController>();
|
readonly _activeProgressControllers = new Set<ProgressController>();
|
||||||
readonly _options: channels.BrowserNewContextParams;
|
readonly _options: types.BrowserContextOptions;
|
||||||
_requestInterceptor?: network.RouteHandler;
|
_requestInterceptor?: network.RouteHandler;
|
||||||
private _isPersistentContext: boolean;
|
private _isPersistentContext: boolean;
|
||||||
private _closedStatus: 'open' | 'closing' | 'closed' = 'open';
|
private _closedStatus: 'open' | 'closing' | 'closed' = 'open';
|
||||||
|
|
@ -93,7 +93,7 @@ export abstract class BrowserContext extends SdkObject {
|
||||||
readonly clock: Clock;
|
readonly clock: Clock;
|
||||||
_clientCertificatesProxy: ClientCertificatesProxy | undefined;
|
_clientCertificatesProxy: ClientCertificatesProxy | undefined;
|
||||||
|
|
||||||
constructor(browser: Browser, options: channels.BrowserNewContextParams, browserContextId: string | undefined) {
|
constructor(browser: Browser, options: types.BrowserContextOptions, browserContextId: string | undefined) {
|
||||||
super(browser, 'browser-context');
|
super(browser, 'browser-context');
|
||||||
this.attribution.context = this;
|
this.attribution.context = this;
|
||||||
this._browser = browser;
|
this._browser = browser;
|
||||||
|
|
@ -659,19 +659,16 @@ export function assertBrowserContextIsNotOwned(context: BrowserContext) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createClientCertificatesProxyIfNeeded(options: channels.BrowserNewContextOptions, browserOptions?: BrowserOptions) {
|
export async function createClientCertificatesProxyIfNeeded(options: types.BrowserContextOptions, browserOptions?: BrowserOptions) {
|
||||||
if (!options.clientCertificates?.length)
|
if (!options.clientCertificates?.length)
|
||||||
return;
|
return;
|
||||||
if ((options.proxy?.server && options.proxy?.server !== 'per-context') || (browserOptions?.proxy?.server && browserOptions?.proxy?.server !== 'http://per-context'))
|
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');
|
throw new Error('Cannot specify both proxy and clientCertificates');
|
||||||
verifyClientCertificates(options.clientCertificates);
|
verifyClientCertificates(options.clientCertificates);
|
||||||
const clientCertificatesProxy = new ClientCertificatesProxy(options);
|
return new ClientCertificatesProxy(options);
|
||||||
options.proxy = { server: await clientCertificatesProxy.listen() };
|
|
||||||
options.ignoreHTTPSErrors = true;
|
|
||||||
return clientCertificatesProxy;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function validateBrowserContextOptions(options: channels.BrowserNewContextParams, browserOptions: BrowserOptions) {
|
export function validateBrowserContextOptions(options: types.BrowserContextOptions, browserOptions: BrowserOptions) {
|
||||||
if (options.noDefaultViewport && options.deviceScaleFactor !== undefined)
|
if (options.noDefaultViewport && options.deviceScaleFactor !== undefined)
|
||||||
throw new Error(`"deviceScaleFactor" option is not supported with null "viewport"`);
|
throw new Error(`"deviceScaleFactor" option is not supported with null "viewport"`);
|
||||||
if (options.noDefaultViewport && !!options.isMobile)
|
if (options.noDefaultViewport && !!options.isMobile)
|
||||||
|
|
@ -720,7 +717,7 @@ export function verifyGeolocation(geolocation?: types.Geolocation) {
|
||||||
throw new Error(`geolocation.accuracy: precondition 0 <= ACCURACY failed.`);
|
throw new Error(`geolocation.accuracy: precondition 0 <= ACCURACY failed.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function verifyClientCertificates(clientCertificates?: channels.BrowserNewContextParams['clientCertificates']) {
|
export function verifyClientCertificates(clientCertificates?: types.BrowserContextOptions['clientCertificates']) {
|
||||||
if (!clientCertificates)
|
if (!clientCertificates)
|
||||||
return;
|
return;
|
||||||
for (const cert of clientCertificates) {
|
for (const cert of clientCertificates) {
|
||||||
|
|
|
||||||
|
|
@ -92,27 +92,26 @@ export abstract class BrowserType extends SdkObject {
|
||||||
return browser;
|
return browser;
|
||||||
}
|
}
|
||||||
|
|
||||||
async launchPersistentContext(metadata: CallMetadata, userDataDir: string, options: channels.BrowserTypeLaunchPersistentContextOptions & { useWebSocket?: boolean }): Promise<BrowserContext> {
|
async launchPersistentContext(metadata: CallMetadata, userDataDir: string, persistentContextOptions: channels.BrowserTypeLaunchPersistentContextOptions & { useWebSocket?: boolean }): Promise<BrowserContext> {
|
||||||
options = this._validateLaunchOptions(options);
|
const launchOptions = this._validateLaunchOptions(persistentContextOptions);
|
||||||
if (this._useBidi)
|
if (this._useBidi)
|
||||||
options.useWebSocket = true;
|
launchOptions.useWebSocket = true;
|
||||||
const controller = new ProgressController(metadata, this);
|
const controller = new ProgressController(metadata, this);
|
||||||
const persistent: channels.BrowserNewContextParams = { ...options };
|
|
||||||
controller.setLogName('browser');
|
controller.setLogName('browser');
|
||||||
const browser = await controller.run(async progress => {
|
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.
|
// 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)
|
if (clientCertificatesProxy)
|
||||||
options.proxy = persistent.proxy;
|
launchOptions.proxyOverride = await clientCertificatesProxy?.listen();
|
||||||
progress.cleanupWhenAborted(() => clientCertificatesProxy?.close());
|
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;
|
browser._defaultContext!._clientCertificatesProxy = clientCertificatesProxy;
|
||||||
return browser;
|
return browser;
|
||||||
}, TimeoutSettings.launchTimeout(options));
|
}, TimeoutSettings.launchTimeout(launchOptions));
|
||||||
return browser._defaultContext!;
|
return browser._defaultContext!;
|
||||||
}
|
}
|
||||||
|
|
||||||
async _innerLaunchWithRetries(progress: Progress, options: types.LaunchOptions, persistent: channels.BrowserNewContextParams | undefined, protocolLogger: types.ProtocolLogger, userDataDir?: string): Promise<Browser> {
|
async _innerLaunchWithRetries(progress: Progress, options: types.LaunchOptions, persistent: types.BrowserContextOptions | undefined, protocolLogger: types.ProtocolLogger, userDataDir?: string): Promise<Browser> {
|
||||||
try {
|
try {
|
||||||
return await this._innerLaunch(progress, options, persistent, protocolLogger, userDataDir);
|
return await this._innerLaunch(progress, options, persistent, protocolLogger, userDataDir);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -126,7 +125,7 @@ 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: types.BrowserContextOptions | 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 browserLogsCollector = new RecentLogsCollector();
|
const browserLogsCollector = new RecentLogsCollector();
|
||||||
const { browserProcess, userDataDir, artifactsDir, transport } = await this._launchProcess(progress, options, !!persistent, browserLogsCollector, maybeUserDataDir);
|
const { browserProcess, userDataDir, artifactsDir, transport } = await this._launchProcess(progress, options, !!persistent, browserLogsCollector, maybeUserDataDir);
|
||||||
|
|
@ -289,7 +288,7 @@ export abstract class BrowserType extends SdkObject {
|
||||||
throw new Error('Connecting to SELENIUM_REMOTE_URL is only supported by Chromium');
|
throw new Error('Connecting to SELENIUM_REMOTE_URL is only supported by Chromium');
|
||||||
}
|
}
|
||||||
|
|
||||||
private _validateLaunchOptions<Options extends types.LaunchOptions>(options: Options): Options {
|
private _validateLaunchOptions(options: types.LaunchOptions): types.LaunchOptions {
|
||||||
const { devtools = false } = options;
|
const { devtools = false } = options;
|
||||||
let { headless = !devtools, downloadsPath, proxy } = options;
|
let { headless = !devtools, downloadsPath, proxy } = options;
|
||||||
if (debugMode())
|
if (debugMode())
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,6 @@ import { CRDevTools } from './crDevTools';
|
||||||
import type { BrowserOptions, BrowserProcess } from '../browser';
|
import type { BrowserOptions, BrowserProcess } from '../browser';
|
||||||
import { Browser } from '../browser';
|
import { Browser } from '../browser';
|
||||||
import type * as types from '../types';
|
import type * as types from '../types';
|
||||||
import type * as channels from '@protocol/channels';
|
|
||||||
import type { HTTPRequestParams } from '../../utils/network';
|
import type { HTTPRequestParams } from '../../utils/network';
|
||||||
import { fetchData } from '../../utils/network';
|
import { fetchData } from '../../utils/network';
|
||||||
import { getUserAgent } from '../../utils/userAgent';
|
import { getUserAgent } from '../../utils/userAgent';
|
||||||
|
|
@ -98,7 +97,7 @@ export class Chromium extends BrowserType {
|
||||||
await cleanedUp;
|
await cleanedUp;
|
||||||
};
|
};
|
||||||
const browserProcess: BrowserProcess = { close: doClose, kill: doClose };
|
const browserProcess: BrowserProcess = { close: doClose, kill: doClose };
|
||||||
const persistent: channels.BrowserNewContextParams = { noDefaultViewport: true };
|
const persistent: types.BrowserContextOptions = { noDefaultViewport: true };
|
||||||
const browserOptions: BrowserOptions = {
|
const browserOptions: BrowserOptions = {
|
||||||
slowMo: options.slowMo,
|
slowMo: options.slowMo,
|
||||||
name: 'chromium',
|
name: 'chromium',
|
||||||
|
|
@ -287,7 +286,7 @@ export class Chromium extends BrowserType {
|
||||||
}
|
}
|
||||||
|
|
||||||
private _innerDefaultArgs(options: types.LaunchOptions): string[] {
|
private _innerDefaultArgs(options: types.LaunchOptions): string[] {
|
||||||
const { args = [], proxy } = options;
|
const { args = [] } = options;
|
||||||
const userDataDirArg = args.find(arg => arg.startsWith('--user-data-dir'));
|
const userDataDirArg = args.find(arg => arg.startsWith('--user-data-dir'));
|
||||||
if (userDataDirArg)
|
if (userDataDirArg)
|
||||||
throw this._createUserDataDirArgMisuseError('--user-data-dir');
|
throw this._createUserDataDirArgMisuseError('--user-data-dir');
|
||||||
|
|
@ -321,6 +320,7 @@ export class Chromium extends BrowserType {
|
||||||
}
|
}
|
||||||
if (options.chromiumSandbox !== true)
|
if (options.chromiumSandbox !== true)
|
||||||
chromeArguments.push('--no-sandbox');
|
chromeArguments.push('--no-sandbox');
|
||||||
|
const proxy = options.proxyOverride || options.proxy;
|
||||||
if (proxy) {
|
if (proxy) {
|
||||||
const proxyURL = new URL(proxy.server);
|
const proxyURL = new URL(proxy.server);
|
||||||
const isSocks = proxyURL.protocol === 'socks5:';
|
const isSocks = proxyURL.protocol === 'socks5:';
|
||||||
|
|
|
||||||
|
|
@ -100,18 +100,19 @@ export class CRBrowser extends Browser {
|
||||||
this._session.on('Browser.downloadProgress', this._onDownloadProgress.bind(this));
|
this._session.on('Browser.downloadProgress', this._onDownloadProgress.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
async doCreateNewContext(options: channels.BrowserNewContextParams): Promise<BrowserContext> {
|
async doCreateNewContext(options: types.BrowserContextOptions): Promise<BrowserContext> {
|
||||||
|
const proxy = options.proxyOverride || options.proxy;
|
||||||
let proxyBypassList = undefined;
|
let proxyBypassList = undefined;
|
||||||
if (options.proxy) {
|
if (proxy) {
|
||||||
if (process.env.PLAYWRIGHT_DISABLE_FORCED_CHROMIUM_PROXIED_LOOPBACK)
|
if (process.env.PLAYWRIGHT_DISABLE_FORCED_CHROMIUM_PROXIED_LOOPBACK)
|
||||||
proxyBypassList = options.proxy.bypass;
|
proxyBypassList = proxy.bypass;
|
||||||
else
|
else
|
||||||
proxyBypassList = '<-loopback>' + (options.proxy.bypass ? `,${options.proxy.bypass}` : '');
|
proxyBypassList = '<-loopback>' + (proxy.bypass ? `,${proxy.bypass}` : '');
|
||||||
}
|
}
|
||||||
|
|
||||||
const { browserContextId } = await this._session.send('Target.createBrowserContext', {
|
const { browserContextId } = await this._session.send('Target.createBrowserContext', {
|
||||||
disposeOnDetach: true,
|
disposeOnDetach: true,
|
||||||
proxyServer: options.proxy ? options.proxy.server : undefined,
|
proxyServer: proxy ? proxy.server : undefined,
|
||||||
proxyBypassList,
|
proxyBypassList,
|
||||||
});
|
});
|
||||||
const context = new CRBrowserContext(this, browserContextId, options);
|
const context = new CRBrowserContext(this, browserContextId, options);
|
||||||
|
|
@ -340,7 +341,7 @@ export class CRBrowserContext extends BrowserContext {
|
||||||
|
|
||||||
declare readonly _browser: CRBrowser;
|
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);
|
super(browser, options, browserContextId);
|
||||||
this._authenticateProxyViaCredentials();
|
this._authenticateProxyViaCredentials();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -543,7 +543,7 @@ class FrameSession {
|
||||||
const options = this._crPage._browserContext._options;
|
const options = this._crPage._browserContext._options;
|
||||||
if (options.bypassCSP)
|
if (options.bypassCSP)
|
||||||
promises.push(this._client.send('Page.setBypassCSP', { enabled: true }));
|
promises.push(this._client.send('Page.setBypassCSP', { enabled: true }));
|
||||||
if (options.ignoreHTTPSErrors)
|
if (options.ignoreHTTPSErrors || options.internalIgnoreHTTPSErrors)
|
||||||
promises.push(this._client.send('Security.setIgnoreCertificateErrors', { ignore: true }));
|
promises.push(this._client.send('Security.setIgnoreCertificateErrors', { ignore: true }));
|
||||||
if (this._isMainFrame())
|
if (this._isMainFrame())
|
||||||
promises.push(this._updateViewport());
|
promises.push(this._updateViewport());
|
||||||
|
|
@ -1213,7 +1213,7 @@ async function emulateTimezone(session: CRSession, timezoneId: string) {
|
||||||
const contextDelegateSymbol = Symbol('delegate');
|
const contextDelegateSymbol = Symbol('delegate');
|
||||||
|
|
||||||
// Chromium reference: https://source.chromium.org/chromium/chromium/src/+/main:components/embedder_support/user_agent_utils.cc;l=434;drc=70a6711e08e9f9e0d8e4c48e9ba5cab62eb010c2
|
// Chromium reference: https://source.chromium.org/chromium/chromium/src/+/main:components/embedder_support/user_agent_utils.cc;l=434;drc=70a6711e08e9f9e0d8e4c48e9ba5cab62eb010c2
|
||||||
function calculateUserAgentMetadata(options: channels.BrowserNewContextParams) {
|
function calculateUserAgentMetadata(options: types.BrowserContextOptions) {
|
||||||
const ua = options.userAgent;
|
const ua = options.userAgent;
|
||||||
if (!ua)
|
if (!ua)
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ import type { BrowserWindow } from 'electron';
|
||||||
import type { Progress } from '../progress';
|
import type { Progress } from '../progress';
|
||||||
import { ProgressController } from '../progress';
|
import { ProgressController } from '../progress';
|
||||||
import { helper } from '../helper';
|
import { helper } from '../helper';
|
||||||
|
import type * as types from '../types';
|
||||||
import { eventsHelper } from '../../utils/eventsHelper';
|
import { eventsHelper } from '../../utils/eventsHelper';
|
||||||
import type { BrowserOptions, BrowserProcess } from '../browser';
|
import type { BrowserOptions, BrowserProcess } from '../browser';
|
||||||
import type { Playwright } from '../playwright';
|
import type { Playwright } from '../playwright';
|
||||||
|
|
@ -265,7 +266,7 @@ export class Electron extends SdkObject {
|
||||||
close: gracefullyClose,
|
close: gracefullyClose,
|
||||||
kill
|
kill
|
||||||
};
|
};
|
||||||
const contextOptions: channels.BrowserNewContextParams = {
|
const contextOptions: types.BrowserContextOptions = {
|
||||||
...options,
|
...options,
|
||||||
noDefaultViewport: true,
|
noDefaultViewport: true,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ type FetchRequestOptions = {
|
||||||
timeoutSettings: TimeoutSettings;
|
timeoutSettings: TimeoutSettings;
|
||||||
ignoreHTTPSErrors?: boolean;
|
ignoreHTTPSErrors?: boolean;
|
||||||
baseURL?: string;
|
baseURL?: string;
|
||||||
clientCertificates?: channels.BrowserNewContextOptions['clientCertificates'];
|
clientCertificates?: types.BrowserContextOptions['clientCertificates'];
|
||||||
};
|
};
|
||||||
|
|
||||||
type HeadersObject = Readonly<{ [name: string]: string }>;
|
type HeadersObject = Readonly<{ [name: string]: string }>;
|
||||||
|
|
|
||||||
|
|
@ -58,8 +58,9 @@ export class FFBrowser extends Browser {
|
||||||
browser._defaultContext = new FFBrowserContext(browser, undefined, options.persistent);
|
browser._defaultContext = new FFBrowserContext(browser, undefined, options.persistent);
|
||||||
promises.push((browser._defaultContext as FFBrowserContext)._initialize());
|
promises.push((browser._defaultContext as FFBrowserContext)._initialize());
|
||||||
}
|
}
|
||||||
if (options.proxy)
|
const proxy = options.originalLaunchOptions.proxyOverride || options.proxy;
|
||||||
promises.push(browser.session.send('Browser.setBrowserProxy', toJugglerProxyOptions(options.proxy)));
|
if (proxy)
|
||||||
|
promises.push(browser.session.send('Browser.setBrowserProxy', toJugglerProxyOptions(proxy)));
|
||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
return browser;
|
return browser;
|
||||||
}
|
}
|
||||||
|
|
@ -88,7 +89,7 @@ export class FFBrowser extends Browser {
|
||||||
return !this._connection._closed;
|
return !this._connection._closed;
|
||||||
}
|
}
|
||||||
|
|
||||||
async doCreateNewContext(options: channels.BrowserNewContextParams): Promise<BrowserContext> {
|
async doCreateNewContext(options: types.BrowserContextOptions): Promise<BrowserContext> {
|
||||||
if (options.isMobile)
|
if (options.isMobile)
|
||||||
throw new Error('options.isMobile is not supported in Firefox');
|
throw new Error('options.isMobile is not supported in Firefox');
|
||||||
const { browserContextId } = await this.session.send('Browser.createBrowserContext', { removeOnDetach: true });
|
const { browserContextId } = await this.session.send('Browser.createBrowserContext', { removeOnDetach: true });
|
||||||
|
|
@ -172,7 +173,7 @@ export class FFBrowser extends Browser {
|
||||||
export class FFBrowserContext extends BrowserContext {
|
export class FFBrowserContext extends BrowserContext {
|
||||||
declare readonly _browser: FFBrowser;
|
declare readonly _browser: FFBrowser;
|
||||||
|
|
||||||
constructor(browser: FFBrowser, browserContextId: string | undefined, options: channels.BrowserNewContextParams) {
|
constructor(browser: FFBrowser, browserContextId: string | undefined, options: types.BrowserContextOptions) {
|
||||||
super(browser, options, browserContextId);
|
super(browser, options, browserContextId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -205,7 +206,7 @@ export class FFBrowserContext extends BrowserContext {
|
||||||
promises.push(this._browser.session.send('Browser.setUserAgentOverride', { browserContextId, userAgent: this._options.userAgent }));
|
promises.push(this._browser.session.send('Browser.setUserAgentOverride', { browserContextId, userAgent: this._options.userAgent }));
|
||||||
if (this._options.bypassCSP)
|
if (this._options.bypassCSP)
|
||||||
promises.push(this._browser.session.send('Browser.setBypassCSP', { browserContextId, bypassCSP: true }));
|
promises.push(this._browser.session.send('Browser.setBypassCSP', { browserContextId, bypassCSP: true }));
|
||||||
if (this._options.ignoreHTTPSErrors)
|
if (this._options.ignoreHTTPSErrors || this._options.internalIgnoreHTTPSErrors)
|
||||||
promises.push(this._browser.session.send('Browser.setIgnoreHTTPSErrors', { browserContextId, ignoreHTTPSErrors: true }));
|
promises.push(this._browser.session.send('Browser.setIgnoreHTTPSErrors', { browserContextId, ignoreHTTPSErrors: true }));
|
||||||
if (this._options.javaScriptEnabled === false)
|
if (this._options.javaScriptEnabled === false)
|
||||||
promises.push(this._browser.session.send('Browser.setJavaScriptDisabled', { browserContextId, javaScriptDisabled: true }));
|
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', {
|
promises.push(this._browser.session.send('Browser.setContextProxy', {
|
||||||
browserContextId: this._browserContextId,
|
browserContextId: this._browserContextId,
|
||||||
...toJugglerProxyOptions(this._options.proxy)
|
...toJugglerProxyOptions(proxy)
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ import { createSocket, createTLSSocket } from '../utils/happy-eyeballs';
|
||||||
import { escapeHTML, generateSelfSignedCertificate, ManualPromise, rewriteErrorMessage } from '../utils';
|
import { escapeHTML, generateSelfSignedCertificate, ManualPromise, rewriteErrorMessage } from '../utils';
|
||||||
import type { SocksSocketClosedPayload, SocksSocketDataPayload, SocksSocketRequestedPayload } from '../common/socksProxy';
|
import type { SocksSocketClosedPayload, SocksSocketDataPayload, SocksSocketRequestedPayload } from '../common/socksProxy';
|
||||||
import { SocksProxy } from '../common/socksProxy';
|
import { SocksProxy } from '../common/socksProxy';
|
||||||
import type * as channels from '@protocol/channels';
|
import type * as types from './types';
|
||||||
import { debugLogger } from '../utils/debugLogger';
|
import { debugLogger } from '../utils/debugLogger';
|
||||||
|
|
||||||
let dummyServerTlsOptions: tls.TlsOptions | undefined = undefined;
|
let dummyServerTlsOptions: tls.TlsOptions | undefined = undefined;
|
||||||
|
|
@ -235,7 +235,7 @@ export class ClientCertificatesProxy {
|
||||||
alpnCache: ALPNCache;
|
alpnCache: ALPNCache;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
contextOptions: Pick<channels.BrowserNewContextOptions, 'clientCertificates' | 'ignoreHTTPSErrors'>
|
contextOptions: Pick<types.BrowserContextOptions, 'clientCertificates' | 'ignoreHTTPSErrors'>
|
||||||
) {
|
) {
|
||||||
this.alpnCache = new ALPNCache();
|
this.alpnCache = new ALPNCache();
|
||||||
this.ignoreHTTPSErrors = contextOptions.ignoreHTTPSErrors;
|
this.ignoreHTTPSErrors = contextOptions.ignoreHTTPSErrors;
|
||||||
|
|
@ -261,9 +261,9 @@ export class ClientCertificatesProxy {
|
||||||
loadDummyServerCertsIfNeeded();
|
loadDummyServerCertsIfNeeded();
|
||||||
}
|
}
|
||||||
|
|
||||||
_initSecureContexts(clientCertificates: channels.BrowserNewContextOptions['clientCertificates']) {
|
_initSecureContexts(clientCertificates: types.BrowserContextOptions['clientCertificates']) {
|
||||||
// Step 1. Group certificates by origin.
|
// Step 1. Group certificates by origin.
|
||||||
const origin2certs = new Map<string, channels.BrowserNewContextOptions['clientCertificates']>();
|
const origin2certs = new Map<string, types.BrowserContextOptions['clientCertificates']>();
|
||||||
for (const cert of clientCertificates || []) {
|
for (const cert of clientCertificates || []) {
|
||||||
const origin = normalizeOrigin(cert.origin);
|
const origin = normalizeOrigin(cert.origin);
|
||||||
const certs = origin2certs.get(origin) || [];
|
const certs = origin2certs.get(origin) || [];
|
||||||
|
|
@ -282,9 +282,9 @@ export class ClientCertificatesProxy {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async listen(): Promise<string> {
|
public async listen() {
|
||||||
const port = await this._socksProxy.listen(0, '127.0.0.1');
|
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() {
|
public async close() {
|
||||||
|
|
@ -301,7 +301,7 @@ function normalizeOrigin(origin: string): string {
|
||||||
}
|
}
|
||||||
|
|
||||||
function convertClientCertificatesToTLSOptions(
|
function convertClientCertificatesToTLSOptions(
|
||||||
clientCertificates: channels.BrowserNewContextOptions['clientCertificates']
|
clientCertificates: types.BrowserContextOptions['clientCertificates']
|
||||||
): Pick<https.RequestOptions, 'pfx' | 'key' | 'cert'> | undefined {
|
): Pick<https.RequestOptions, 'pfx' | 'key' | 'cert'> | undefined {
|
||||||
if (!clientCertificates || !clientCertificates.length)
|
if (!clientCertificates || !clientCertificates.length)
|
||||||
return;
|
return;
|
||||||
|
|
@ -322,7 +322,7 @@ function convertClientCertificatesToTLSOptions(
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMatchingTLSOptionsForOrigin(
|
export function getMatchingTLSOptionsForOrigin(
|
||||||
clientCertificates: channels.BrowserNewContextOptions['clientCertificates'],
|
clientCertificates: types.BrowserContextOptions['clientCertificates'],
|
||||||
origin: string
|
origin: string
|
||||||
): Pick<https.RequestOptions, 'pfx' | 'key' | 'cert'> | undefined {
|
): Pick<https.RequestOptions, 'pfx' | 'key' | 'cert'> | undefined {
|
||||||
const matchingCerts = clientCertificates?.filter(c =>
|
const matchingCerts = clientCertificates?.filter(c =>
|
||||||
|
|
|
||||||
|
|
@ -150,7 +150,15 @@ export type NormalizedContinueOverrides = {
|
||||||
|
|
||||||
export type EmulatedSize = { viewport: Size, screen: Size };
|
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;
|
||||||
|
internalIgnoreHTTPSErrors?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
export type ProtocolLogger = (direction: 'send' | 'receive', message: object) => void;
|
export type ProtocolLogger = (direction: 'send' | 'receive', message: object) => void;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ export class WebKit extends BrowserType {
|
||||||
}
|
}
|
||||||
|
|
||||||
override defaultArgs(options: types.LaunchOptions, isPersistent: boolean, userDataDir: string): string[] {
|
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'));
|
const userDataDirArg = args.find(arg => arg.startsWith('--user-data-dir'));
|
||||||
if (userDataDirArg)
|
if (userDataDirArg)
|
||||||
throw this._createUserDataDirArgMisuseError('--user-data-dir');
|
throw this._createUserDataDirArgMisuseError('--user-data-dir');
|
||||||
|
|
@ -68,6 +68,7 @@ export class WebKit extends BrowserType {
|
||||||
webkitArguments.push(`--user-data-dir=${userDataDir}`);
|
webkitArguments.push(`--user-data-dir=${userDataDir}`);
|
||||||
else
|
else
|
||||||
webkitArguments.push(`--no-startup-window`);
|
webkitArguments.push(`--no-startup-window`);
|
||||||
|
const proxy = options.proxyOverride || options.proxy;
|
||||||
if (proxy) {
|
if (proxy) {
|
||||||
if (process.platform === 'darwin') {
|
if (process.platform === 'darwin') {
|
||||||
webkitArguments.push(`--proxy=${proxy.server}`);
|
webkitArguments.push(`--proxy=${proxy.server}`);
|
||||||
|
|
|
||||||
|
|
@ -81,12 +81,13 @@ export class WKBrowser extends Browser {
|
||||||
this._didClose();
|
this._didClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
async doCreateNewContext(options: channels.BrowserNewContextParams): Promise<BrowserContext> {
|
async doCreateNewContext(options: types.BrowserContextOptions): Promise<BrowserContext> {
|
||||||
const createOptions = options.proxy ? {
|
const proxy = options.proxyOverride || options.proxy;
|
||||||
// Enable socks5 hostname resolution on Windows. Workaround can be removed once fixed upstream.
|
const createOptions = proxy ? {
|
||||||
|
// Enable socks5 hostname resolution on Windows.
|
||||||
// See https://github.com/microsoft/playwright/issues/20451
|
// See https://github.com/microsoft/playwright/issues/20451
|
||||||
proxyServer: process.platform === 'win32' ? options.proxy.server.replace(/^socks5:\/\//, 'socks5h://') : options.proxy.server,
|
proxyServer: process.platform === 'win32' ? proxy.server.replace(/^socks5:\/\//, 'socks5h://') : proxy.server,
|
||||||
proxyBypassList: options.proxy.bypass
|
proxyBypassList: proxy.bypass
|
||||||
} : undefined;
|
} : undefined;
|
||||||
const { browserContextId } = await this._browserSession.send('Playwright.createContext', createOptions);
|
const { browserContextId } = await this._browserSession.send('Playwright.createContext', createOptions);
|
||||||
options.userAgent = options.userAgent || DEFAULT_USER_AGENT;
|
options.userAgent = options.userAgent || DEFAULT_USER_AGENT;
|
||||||
|
|
@ -206,7 +207,7 @@ export class WKBrowser extends Browser {
|
||||||
export class WKBrowserContext extends BrowserContext {
|
export class WKBrowserContext extends BrowserContext {
|
||||||
declare readonly _browser: WKBrowser;
|
declare readonly _browser: WKBrowser;
|
||||||
|
|
||||||
constructor(browser: WKBrowser, browserContextId: string | undefined, options: channels.BrowserNewContextParams) {
|
constructor(browser: WKBrowser, browserContextId: string | undefined, options: types.BrowserContextOptions) {
|
||||||
super(browser, options, browserContextId);
|
super(browser, options, browserContextId);
|
||||||
this._validateEmulatedViewport(options.viewport);
|
this._validateEmulatedViewport(options.viewport);
|
||||||
this._authenticateProxyViaHeader();
|
this._authenticateProxyViaHeader();
|
||||||
|
|
@ -221,7 +222,7 @@ export class WKBrowserContext extends BrowserContext {
|
||||||
downloadPath: this._browser.options.downloadsPath,
|
downloadPath: this._browser.options.downloadsPath,
|
||||||
browserContextId
|
browserContextId
|
||||||
}));
|
}));
|
||||||
if (this._options.ignoreHTTPSErrors)
|
if (this._options.ignoreHTTPSErrors || this._options.internalIgnoreHTTPSErrors)
|
||||||
promises.push(this._browser._browserSession.send('Playwright.setIgnoreCertificateErrors', { browserContextId, ignore: true }));
|
promises.push(this._browser._browserSession.send('Playwright.setIgnoreCertificateErrors', { browserContextId, ignore: true }));
|
||||||
if (this._options.locale)
|
if (this._options.locale)
|
||||||
promises.push(this._browser._browserSession.send('Playwright.setLanguages', { browserContextId, languages: [this._options.locale] }));
|
promises.push(this._browser._browserSession.send('Playwright.setLanguages', { browserContextId, languages: [this._options.locale] }));
|
||||||
|
|
|
||||||
|
|
@ -546,6 +546,7 @@ test.describe('browser', () => {
|
||||||
keyPath: asset('client-certificates/client/trusted/key.pem'),
|
keyPath: asset('client-certificates/client/trusted/key.pem'),
|
||||||
};
|
};
|
||||||
const page = await browser.newPage({
|
const page = await browser.newPage({
|
||||||
|
ignoreHTTPSErrors: true,
|
||||||
clientCertificates: [{
|
clientCertificates: [{
|
||||||
origin: new URL(serverURL).origin,
|
origin: new URL(serverURL).origin,
|
||||||
...baseOptions,
|
...baseOptions,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue