diff --git a/docs/api.md b/docs/api.md index d24a210d6e..971d333d75 100644 --- a/docs/api.md +++ b/docs/api.md @@ -3997,6 +3997,7 @@ This methods attaches Playwright to an existing browser instance. - `executablePath` <[string]> Path to a browser executable to run instead of the bundled one. If `executablePath` is a relative path, then it is resolved relative to [current working directory](https://nodejs.org/api/process.html#process_process_cwd). Note that Playwright only works with the bundled Chromium, Firefox or WebKit, use at your own risk. - `args` <[Array]<[string]>> Additional arguments to pass to the browser instance. The list of Chromium flags can be found [here](http://peter.sh/experiments/chromium-command-line-switches/). - `ignoreDefaultArgs` <[boolean]|[Array]<[string]>> If `true`, Playwright does not pass its own configurations args and only uses the ones from `args`. If an array is given, then filters out the given default arguments. Dangerous option; use with care. Defaults to `false`. + - `firefoxUserPrefs` <[Object]> Firefox user preferences. Learn more about the Firefox user preferences at [`about:config`](https://support.mozilla.org/en-US/kb/about-config-editor-firefox). - `handleSIGINT` <[boolean]> Close the browser process on Ctrl-C. Defaults to `true`. - `handleSIGTERM` <[boolean]> Close the browser process on SIGTERM. Defaults to `true`. - `handleSIGHUP` <[boolean]> Close the browser process on SIGHUP. Defaults to `true`. @@ -4072,6 +4073,7 @@ Launches browser that uses persistent storage located at `userDataDir` and retur - `executablePath` <[string]> Path to a browser executable to run instead of the bundled one. If `executablePath` is a relative path, then it is resolved relative to [current working directory](https://nodejs.org/api/process.html#process_process_cwd). **BEWARE**: Playwright is only guaranteed to work with the bundled Chromium, Firefox or WebKit, use at your own risk. - `args` <[Array]<[string]>> Additional arguments to pass to the browser instance. The list of Chromium flags can be found [here](http://peter.sh/experiments/chromium-command-line-switches/). - `ignoreDefaultArgs` <[boolean]|[Array]<[string]>> If `true`, then do not use any of the default arguments. If an array is given, then filter out the given default arguments. Dangerous option; use with care. Defaults to `false`. + - `firefoxUserPrefs` <[Object]> Firefox user preferences. Learn more about the Firefox user preferences at [`about:config`](https://support.mozilla.org/en-US/kb/about-config-editor-firefox). - `handleSIGINT` <[boolean]> Close the browser process on Ctrl-C. Defaults to `true`. - `handleSIGTERM` <[boolean]> Close the browser process on SIGTERM. Defaults to `true`. - `handleSIGHUP` <[boolean]> Close the browser process on SIGHUP. Defaults to `true`. diff --git a/src/server/browserType.ts b/src/server/browserType.ts index 95a20f4250..1fb414f8b0 100644 --- a/src/server/browserType.ts +++ b/src/server/browserType.ts @@ -36,6 +36,10 @@ export type BrowserArgOptions = { devtools?: boolean, }; +export type FirefoxUserPrefsOptions = { + firefoxUserPrefs?: { [key: string]: string | number | boolean }, +}; + type LaunchOptionsBase = BrowserArgOptions & { executablePath?: string, ignoreDefaultArgs?: boolean | string[], @@ -64,8 +68,8 @@ type LaunchServerOptions = LaunchOptionsBase & { port?: number }; export interface BrowserType { executablePath(): string; name(): string; - launch(options?: LaunchOptions): Promise; - launchServer(options?: LaunchServerOptions): Promise; + launch(options?: LaunchOptions & FirefoxUserPrefsOptions): Promise; + launchServer(options?: LaunchServerOptions & FirefoxUserPrefsOptions): Promise; launchPersistentContext(userDataDir: string, options?: LaunchOptions & PersistentContextOptions): Promise; connect(options: ConnectOptions): Promise; } diff --git a/src/server/firefox.ts b/src/server/firefox.ts index ec0fae80be..faf6980229 100644 --- a/src/server/firefox.ts +++ b/src/server/firefox.ts @@ -16,13 +16,14 @@ */ import * as os from 'os'; +import * as fs from 'fs'; import * as path from 'path'; import * as ws from 'ws'; import { FFBrowser } from '../firefox/ffBrowser'; import { kBrowserCloseMessageId } from '../firefox/ffConnection'; import { helper } from '../helper'; import { WebSocketWrapper } from './browserServer'; -import { BrowserArgOptions, BrowserTypeBase, processBrowserArgOptions } from './browserType'; +import { BrowserArgOptions, BrowserTypeBase, processBrowserArgOptions, FirefoxUserPrefsOptions } from './browserType'; import { Env } from './processLauncher'; import { ConnectionTransport, SequenceNumberMixer } from '../transport'; import { InnerLogger, logError } from '../logger'; @@ -56,7 +57,7 @@ export class Firefox extends BrowserTypeBase { return wrapTransportWithWebSocket(transport, logger, port); } - _defaultArgs(options: BrowserArgOptions, isPersistent: boolean, userDataDir: string): string[] { + _defaultArgs(options: BrowserArgOptions & FirefoxUserPrefsOptions, isPersistent: boolean, userDataDir: string): string[] { const { devtools, headless } = processBrowserArgOptions(options); const { args = [] } = options; if (devtools) @@ -66,7 +67,12 @@ export class Firefox extends BrowserTypeBase { throw new Error('Pass userDataDir parameter instead of specifying -profile argument'); if (args.find(arg => arg.startsWith('-juggler'))) throw new Error('Use the port parameter instead of -juggler argument'); - + if (options.firefoxUserPrefs) { + const lines: string[] = []; + for (const [name, value] of Object.entries(options.firefoxUserPrefs)) + lines.push(`user_pref(${JSON.stringify(name)}, ${JSON.stringify(value)});`); + fs.writeFileSync(path.join(userDataDir, 'user.js'), lines.join('\n')); + } const firefoxArguments = ['-no-remote']; if (headless) { firefoxArguments.push('-headless'); diff --git a/test/firefox/launcher.spec.js b/test/firefox/launcher.spec.js new file mode 100644 index 0000000000..12637e0181 --- /dev/null +++ b/test/firefox/launcher.spec.js @@ -0,0 +1,37 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * 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. + */ + +const path = require('path'); +const utils = require('../utils'); +const {makeUserDataDir, removeUserDataDir} = utils; +const {FFOX, CHROMIUM, WEBKIT, WIN} = utils.testOptions(browserType); + +describe('launcher', function() { + it('should pass firefox user preferences', async({browserType, defaultBrowserOptions}) => { + const browser = await browserType.launch({ + ...defaultBrowserOptions, + firefoxUserPrefs: { + 'network.proxy.type': 1, + 'network.proxy.http': '127.0.0.1', + 'network.proxy.http_port': 3333, + } + }); + const page = await browser.newPage(); + const error = await page.goto('http://example.com').catch(e => e); + expect(error.message).toContain('NS_ERROR_PROXY_CONNECTION_REFUSED'); + await browser.close(); + }); +}); diff --git a/test/test.config.js b/test/test.config.js index c7c5e6f54c..4a8d5f833a 100644 --- a/test/test.config.js +++ b/test/test.config.js @@ -243,6 +243,15 @@ module.exports = { environments: [customEnvironment], }, + { + files: [ + './firefox/launcher.spec.js', + ], + browsers: ['firefox'], + title: '[Firefox]', + environments: [customEnvironment], + }, + { files: [ './electron/electron.spec.js',