feat(launcher): allow options to be passed into puppeteer.defaultArgs (#2950)

This commit is contained in:
Joel Einbinder 2018-08-07 13:22:04 -07:00 committed by Andrey Lushnikov
parent 2e0007669d
commit 6825088644
5 changed files with 85 additions and 70 deletions

View File

@ -16,7 +16,7 @@ Next Release: **Aug 9, 2018**
- [class: Puppeteer](#class-puppeteer) - [class: Puppeteer](#class-puppeteer)
* [puppeteer.connect(options)](#puppeteerconnectoptions) * [puppeteer.connect(options)](#puppeteerconnectoptions)
* [puppeteer.createBrowserFetcher([options])](#puppeteercreatebrowserfetcheroptions) * [puppeteer.createBrowserFetcher([options])](#puppeteercreatebrowserfetcheroptions)
* [puppeteer.defaultArgs()](#puppeteerdefaultargs) * [puppeteer.defaultArgs([options])](#puppeteerdefaultargsoptions)
* [puppeteer.executablePath()](#puppeteerexecutablepath) * [puppeteer.executablePath()](#puppeteerexecutablepath)
* [puppeteer.launch([options])](#puppeteerlaunchoptions) * [puppeteer.launch([options])](#puppeteerlaunchoptions)
- [class: BrowserFetcher](#class-browserfetcher) - [class: BrowserFetcher](#class-browserfetcher)
@ -378,9 +378,20 @@ This methods attaches Puppeteer to an existing Chromium instance.
- `platform` <[string]> Possible values are: `mac`, `win32`, `win64`, `linux`. Defaults to the current platform. - `platform` <[string]> Possible values are: `mac`, `win32`, `win64`, `linux`. Defaults to the current platform.
- returns: <[BrowserFetcher]> - returns: <[BrowserFetcher]>
#### puppeteer.defaultArgs() #### puppeteer.defaultArgs([options])
- `options` <[Object]> Set of configurable options to set on the browser. Can have the following fields:
- `headless` <[boolean]> Whether to run browser in [headless mode](https://developers.google.com/web/updates/2017/04/headless-chrome). Defaults to `true` unless the `devtools` option is `true`.
- `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/).
- `userDataDir` <[string]> Path to a [User Data Directory](https://chromium.googlesource.com/chromium/src/+/master/docs/user_data_dir.md).
- `devtools` <[boolean]> Whether to auto-open a DevTools panel for each tab. If this option is `true`, the `headless` option will be set `false`.
- returns: <[Array]<[string]>> The default flags that Chromium will be launched with. - returns: <[Array]<[string]>> The default flags that Chromium will be launched with.
`puppeteer.defaultArgs` can be used to turn off some flags that Puppeteer usually launches Chromium with:
```js
const customArgs = puppeteer.defaultArgs().filter(arg => arg !== '--mute-audio');
const browser = await puppeteer.launch({ignoreDefaultArgs: true, args: customArgs});
```
#### puppeteer.executablePath() #### puppeteer.executablePath()
- returns: <[string]> A path where Puppeteer expects to find bundled Chromium. Chromium might not exist there if the download was skipped with [`PUPPETEER_SKIP_CHROMIUM_DOWNLOAD`](#environment-variables). - returns: <[string]> A path where Puppeteer expects to find bundled Chromium. Chromium might not exist there if the download was skipped with [`PUPPETEER_SKIP_CHROMIUM_DOWNLOAD`](#environment-variables).
@ -398,7 +409,7 @@ This methods attaches Puppeteer to an existing Chromium instance.
- `hasTouch`<[boolean]> Specifies if viewport supports touch events. Defaults to `false` - `hasTouch`<[boolean]> Specifies if viewport supports touch events. Defaults to `false`
- `isLandscape` <[boolean]> Specifies if viewport is in landscape mode. Defaults to `false`. - `isLandscape` <[boolean]> Specifies if viewport is in landscape mode. Defaults to `false`.
- `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/). - `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]> Do not use [`puppeteer.defaultArgs()`](#puppeteerdefaultargs). Dangerous option; use with care. Defaults to `false`. - `ignoreDefaultArgs` <[boolean]> Do not use [`puppeteer.defaultArgs()`](#puppeteerdefaultargs-options). Dangerous option; use with care. Defaults to `false`.
- `handleSIGINT` <[boolean]> Close the browser process on Ctrl-C. Defaults to `true`. - `handleSIGINT` <[boolean]> Close the browser process on Ctrl-C. Defaults to `true`.
- `handleSIGTERM` <[boolean]> Close the browser process on SIGTERM. 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`. - `handleSIGHUP` <[boolean]> Close the browser process on SIGHUP. Defaults to `true`.

View File

@ -297,9 +297,3 @@ helper.tracePublicAPI(BrowserContext);
helper.tracePublicAPI(Browser); helper.tracePublicAPI(Browser);
module.exports = {Browser, BrowserContext}; module.exports = {Browser, BrowserContext};
/**
* @typedef {Object} BrowserOptions
* @property {boolean=} setDefaultViewport
* @property {boolean=} ignoreHTTPSErrors
*/

View File

@ -48,9 +48,6 @@ const DEFAULT_ARGS = [
'--metrics-recording-only', '--metrics-recording-only',
'--no-first-run', '--no-first-run',
'--safebrowsing-disable-auto-update', '--safebrowsing-disable-auto-update',
];
const AUTOMATION_ARGS = [
'--enable-automation', '--enable-automation',
'--password-store=basic', '--password-store=basic',
'--use-mock-keychain', '--use-mock-keychain',
@ -58,52 +55,43 @@ const AUTOMATION_ARGS = [
class Launcher { class Launcher {
/** /**
* @param {!(LaunchOptions & BrowserOptions)=} options * @param {!(LaunchOptions & ChromeArgOptions & BrowserOptions)=} options
* @return {!Promise<!Browser>} * @return {!Promise<!Browser>}
*/ */
static async launch(options) { static async launch(options = {}) {
options = Object.assign({}, options || {}); const {
ignoreDefaultArgs = false,
args = [],
dumpio = false,
executablePath = null,
pipe = false,
env = process.env,
handleSIGINT = true,
handleSIGTERM = true,
handleSIGHUP = true,
ignoreHTTPSErrors = false,
defaultViewport = {width: 800, height: 600},
slowMo = 0,
timeout = 30000
} = options;
const chromeArguments = !ignoreDefaultArgs ? this.defaultArgs(options) : args;
let temporaryUserDataDir = null; let temporaryUserDataDir = null;
const chromeArguments = [];
if (!options.ignoreDefaultArgs)
chromeArguments.push(...DEFAULT_ARGS);
if (!options.ignoreDefaultArgs)
chromeArguments.push(...AUTOMATION_ARGS);
if (!options.ignoreDefaultArgs || !chromeArguments.some(argument => argument.startsWith('--remote-debugging-'))) if (!chromeArguments.some(argument => argument.startsWith('--remote-debugging-')))
chromeArguments.push(options.pipe ? '--remote-debugging-pipe' : '--remote-debugging-port=0'); chromeArguments.push(pipe ? '--remote-debugging-pipe' : '--remote-debugging-port=0');
if (!chromeArguments.some(arg => arg.startsWith('--user-data-dir'))) {
if (!options.args || !options.args.some(arg => arg.startsWith('--user-data-dir'))) {
if (!options.userDataDir)
temporaryUserDataDir = await mkdtempAsync(CHROME_PROFILE_PATH); temporaryUserDataDir = await mkdtempAsync(CHROME_PROFILE_PATH);
chromeArguments.push(`--user-data-dir=${temporaryUserDataDir}`);
}
chromeArguments.push(`--user-data-dir=${options.userDataDir || temporaryUserDataDir}`); let chromeExecutable = executablePath;
} if (!executablePath) {
if (options.devtools === true) {
chromeArguments.push('--auto-open-devtools-for-tabs');
options.headless = false;
}
if (typeof options.headless !== 'boolean' || options.headless) {
chromeArguments.push(
'--headless',
'--hide-scrollbars',
'--mute-audio'
);
if (os.platform() === 'win32')
chromeArguments.push('--disable-gpu');
}
if (Array.isArray(options.args) && options.args.every(arg => arg.startsWith('-')))
chromeArguments.push('about:blank');
let chromeExecutable = options.executablePath;
if (typeof chromeExecutable !== 'string') {
const browserFetcher = new BrowserFetcher(); const browserFetcher = new BrowserFetcher();
const revisionInfo = browserFetcher.revisionInfo(ChromiumRevision); const revisionInfo = browserFetcher.revisionInfo(ChromiumRevision);
assert(revisionInfo.local, `Chromium revision is not downloaded. Run "npm install" or "yarn install"`); assert(revisionInfo.local, `Chromium revision is not downloaded. Run "npm install" or "yarn install"`);
chromeExecutable = revisionInfo.executablePath; chromeExecutable = revisionInfo.executablePath;
} }
if (Array.isArray(options.args))
chromeArguments.push(...options.args);
const usePipe = chromeArguments.includes('--remote-debugging-pipe'); const usePipe = chromeArguments.includes('--remote-debugging-pipe');
const stdio = usePipe ? ['ignore', 'ignore', 'ignore', 'pipe', 'pipe'] : ['pipe', 'pipe', 'pipe']; const stdio = usePipe ? ['ignore', 'ignore', 'ignore', 'pipe', 'pipe'] : ['pipe', 'pipe', 'pipe'];
@ -115,12 +103,12 @@ class Launcher {
// process group, making it possible to kill child process tree with `.kill(-pid)` command. // process group, making it possible to kill child process tree with `.kill(-pid)` command.
// @see https://nodejs.org/api/child_process.html#child_process_options_detached // @see https://nodejs.org/api/child_process.html#child_process_options_detached
detached: process.platform !== 'win32', detached: process.platform !== 'win32',
env: options.env || process.env, env,
stdio stdio
} }
); );
if (options.dumpio) { if (dumpio) {
chromeProcess.stderr.pipe(process.stderr); chromeProcess.stderr.pipe(process.stderr);
chromeProcess.stdout.pipe(process.stdout); chromeProcess.stdout.pipe(process.stdout);
} }
@ -141,22 +129,16 @@ class Launcher {
}); });
const listeners = [ helper.addEventListener(process, 'exit', killChrome) ]; const listeners = [ helper.addEventListener(process, 'exit', killChrome) ];
if (options.handleSIGINT !== false) if (handleSIGINT)
listeners.push(helper.addEventListener(process, 'SIGINT', () => { killChrome(); process.exit(130); })); listeners.push(helper.addEventListener(process, 'SIGINT', () => { killChrome(); process.exit(130); }));
if (options.handleSIGTERM !== false) if (handleSIGTERM)
listeners.push(helper.addEventListener(process, 'SIGTERM', gracefullyCloseChrome)); listeners.push(helper.addEventListener(process, 'SIGTERM', gracefullyCloseChrome));
if (options.handleSIGHUP !== false) if (handleSIGHUP)
listeners.push(helper.addEventListener(process, 'SIGHUP', gracefullyCloseChrome)); listeners.push(helper.addEventListener(process, 'SIGHUP', gracefullyCloseChrome));
/** @type {?Connection} */ /** @type {?Connection} */
let connection = null; let connection = null;
try { try {
const {
ignoreHTTPSErrors = false,
defaultViewport = {width: 800, height: 600},
slowMo = 0
} = options;
if (!usePipe) { if (!usePipe) {
const timeout = helper.isNumber(options.timeout) ? options.timeout : 30000;
const browserWSEndpoint = await waitForWSEndpoint(chromeProcess, timeout); const browserWSEndpoint = await waitForWSEndpoint(chromeProcess, timeout);
connection = await Connection.createForWebSocket(browserWSEndpoint, slowMo); connection = await Connection.createForWebSocket(browserWSEndpoint, slowMo);
} else { } else {
@ -228,10 +210,34 @@ class Launcher {
} }
/** /**
* @param {!ChromeArgOptions=} options
* @return {!Array<string>} * @return {!Array<string>}
*/ */
static defaultArgs() { static defaultArgs(options = {}) {
return DEFAULT_ARGS.concat(AUTOMATION_ARGS); const {
devtools = false,
headless = !devtools,
args = [],
userDataDir = null
} = options;
const chromeArguments = [...DEFAULT_ARGS];
if (userDataDir)
chromeArguments.push(`--user-data-dir=${userDataDir}`);
if (devtools)
chromeArguments.push('--auto-open-devtools-for-tabs');
if (headless) {
chromeArguments.push(
'--headless',
'--hide-scrollbars',
'--mute-audio'
);
if (os.platform() === 'win32')
chromeArguments.push('--disable-gpu');
}
if (args.every(arg => arg.startsWith('-')))
chromeArguments.push('about:blank');
chromeArguments.push(...args);
return chromeArguments;
} }
/** /**
@ -317,19 +323,23 @@ function waitForWSEndpoint(chromeProcess, timeout) {
} }
/** /**
* @typedef {Object} LaunchOptions * @typedef {Object} ChromeArgOptions
* @property {boolean=} headless * @property {boolean=} headless
* @property {Array<string>=} args
* @property {string=} userDataDir
* @property {boolean=} devtools
*/
/**
* @typedef {Object} LaunchOptions
* @property {string=} executablePath * @property {string=} executablePath
* @property {!Array<string>=} args
* @property {boolean=} ignoreDefaultArgs * @property {boolean=} ignoreDefaultArgs
* @property {boolean=} handleSIGINT * @property {boolean=} handleSIGINT
* @property {boolean=} handleSIGTERM * @property {boolean=} handleSIGTERM
* @property {boolean=} handleSIGHUP * @property {boolean=} handleSIGHUP
* @property {number=} timeout * @property {number=} timeout
* @property {boolean=} dumpio * @property {boolean=} dumpio
* @property {string=} userDataDir
* @property {!Object<string, string | undefined>=} env * @property {!Object<string, string | undefined>=} env
* @property {boolean=} devtools
* @property {boolean=} pipe * @property {boolean=} pipe
*/ */

View File

@ -44,8 +44,8 @@ module.exports = class {
/** /**
* @return {!Array<string>} * @return {!Array<string>}
*/ */
static defaultArgs() { static defaultArgs(options) {
return Launcher.defaultArgs(); return Launcher.defaultArgs(options);
} }
/** /**

View File

@ -60,7 +60,6 @@ module.exports.addTests = function({testRunner, expect, PROJECT_ROOT, defaultBro
await rmAsync(downloadsFolder); await rmAsync(downloadsFolder);
}); });
}); });
describe('Puppeteer.launch', function() { describe('Puppeteer.launch', function() {
it('should reject all promises when browser is closed', async() => { it('should reject all promises when browser is closed', async() => {
const browser = await puppeteer.launch(defaultBrowserOptions); const browser = await puppeteer.launch(defaultBrowserOptions);
@ -135,8 +134,10 @@ module.exports.addTests = function({testRunner, expect, PROJECT_ROOT, defaultBro
await rmAsync(userDataDir).catch(e => {}); await rmAsync(userDataDir).catch(e => {});
}); });
it('should return the default chrome arguments', async() => { it('should return the default chrome arguments', async() => {
const args = puppeteer.defaultArgs(); expect(puppeteer.defaultArgs()).toContain('--no-first-run');
expect(args).toContain('--no-first-run'); expect(puppeteer.defaultArgs()).toContain('--headless');
expect(puppeteer.defaultArgs({headless: false})).not.toContain('--headless');
expect(puppeteer.defaultArgs({userDataDir: 'foo'})).toContain('--user-data-dir=foo');
}); });
it('should dump browser process stderr', async({server}) => { it('should dump browser process stderr', async({server}) => {
const dumpioTextToLog = 'MAGIC_DUMPIO_TEST'; const dumpioTextToLog = 'MAGIC_DUMPIO_TEST';
@ -183,7 +184,6 @@ module.exports.addTests = function({testRunner, expect, PROJECT_ROOT, defaultBro
}); });
it('should support the pipe argument', async() => { it('should support the pipe argument', async() => {
const options = Object.assign({}, defaultBrowserOptions); const options = Object.assign({}, defaultBrowserOptions);
options.ignoreDefaultArgs = true;
options.args = ['--remote-debugging-pipe'].concat(options.args); options.args = ['--remote-debugging-pipe'].concat(options.args);
const browser = await puppeteer.launch(options); const browser = await puppeteer.launch(options);
expect(browser.wsEndpoint()).toBe(''); expect(browser.wsEndpoint()).toBe('');