diff --git a/docs/api.md b/docs/api.md index 0910e4a4..b31771cc 100644 --- a/docs/api.md +++ b/docs/api.md @@ -342,12 +342,13 @@ However, you should use `puppeteer-core` if: ### Environment Variables Puppeteer looks for certain [environment variables](https://en.wikipedia.org/wiki/Environment_variable) to aid its operations. -If puppeteer doesn't find them in the environment, a lowercased variant of these variables will be used from the [npm config](https://docs.npmjs.com/cli/config). +If Puppeteer doesn't find them in the environment during the installation step, a lowercased variant of these variables will be used from the [npm config](https://docs.npmjs.com/cli/config). - `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` - defines HTTP proxy settings that are used to download and run Chromium. - `PUPPETEER_SKIP_CHROMIUM_DOWNLOAD` - do not download bundled Chromium during installation step. - `PUPPETEER_DOWNLOAD_HOST` - overwrite host part of URL that is used to download Chromium -- `PUPPETEER_CHROMIUM_REVISION` - specify a certain version of chrome you'd like puppeteer to use during the installation step. +- `PUPPETEER_CHROMIUM_REVISION` - specify a certain version of Chromium you'd like Puppeteer to use. See [puppeteer.launch([options])](#puppeteerlaunchoptions) on how executable path is inferred. **BEWARE**: Puppeteer is only [guaranteed to work](https://github.com/GoogleChrome/puppeteer/#q-why-doesnt-puppeteer-vxxx-work-with-chromium-vyyy) with the bundled Chromium, use at your own risk. +- `PUPPETEER_EXECUTABLE_PATH` - specify an executable path to be used in `puppeteer.launch`. See [puppeteer.launch([options])](#puppeteerlaunchoptions) on how the executable path is inferred. **BEWARE**: Puppeteer is only [guaranteed to work](https://github.com/GoogleChrome/puppeteer/#q-why-doesnt-puppeteer-vxxx-work-with-chromium-vyyy) with the bundled Chromium, use at your own risk. > **NOTE** PUPPETEER_* env variables are not accounted for in the [`puppeteer-core`](https://www.npmjs.com/package/puppeteer-core) package. @@ -462,7 +463,7 @@ The default flags that Chromium will be launched with. - `options` <[Object]> Set of configurable options to set on the browser. Can have the following fields: - `ignoreHTTPSErrors` <[boolean]> Whether to ignore HTTPS errors during navigation. Defaults to `false`. - `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`. - - `executablePath` <[string]> Path to a Chromium or Chrome executable to run instead of the bundled Chromium. If `executablePath` is a relative path, then it is resolved relative to [current working directory](https://nodejs.org/api/process.html#process_process_cwd). + - `executablePath` <[string]> Path to a Chromium or Chrome executable to run instead of the bundled Chromium. 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**: Puppeteer is only [guaranteed to work](https://github.com/GoogleChrome/puppeteer/#q-why-doesnt-puppeteer-vxxx-work-with-chromium-vyyy) with the bundled Chromium, use at your own risk. - `slowMo` <[number]> Slows down Puppeteer operations by the specified amount of milliseconds. Useful so that you can see what is going on. - `defaultViewport` Sets a consistent viewport for each page. Defaults to an 800x600 viewport. `null` disables the default viewport. - `width` <[number]> page width in pixels. @@ -484,13 +485,8 @@ The default flags that Chromium will be launched with. - `pipe` <[boolean]> Connects to the browser over a pipe instead of a WebSocket. Defaults to `false`. - returns: <[Promise]<[Browser]>> Promise which resolves to browser instance. -The method combines 3 steps: -1. Infer a set of flags to launch Chromium with using `puppeteer.defaultArgs()`. -2. Launch a browser and start managing its process according to `executablePath`, `handleSIGINT`, `dumpio` and other options. -3. Create an instance of [Browser] class and initialize it with regards to `defaultViewport`, `slowMo` and `ignoreHTTPSErrors`. -`ignoreDefaultArgs` option can be used to customize behavior on the (1) step. For example, to filter out -`--mute-audio` from default arguments: +You can use `ignoreDefaultArgs` to filter out `--mute-audio` from default arguments: ```js const browser = await puppeteer.launch({ ignoreDefaultArgs: ['--mute-audio'] diff --git a/lib/Launcher.js b/lib/Launcher.js index 9c6a2c52..c8fff027 100644 --- a/lib/Launcher.js +++ b/lib/Launcher.js @@ -22,7 +22,7 @@ const {Connection} = require('./Connection'); const {Browser} = require('./Browser'); const readline = require('readline'); const fs = require('fs'); -const {helper, assert, debugError} = require('./helper'); +const {helper, debugError} = require('./helper'); const ChromiumRevision = require(path.join(helper.projectRoot(), 'package.json')).puppeteer.chromium_revision; const {TimeoutError} = require('./Errors'); @@ -95,10 +95,10 @@ class Launcher { let chromeExecutable = executablePath; if (!executablePath) { - const browserFetcher = new BrowserFetcher(); - const revisionInfo = browserFetcher.revisionInfo(ChromiumRevision); - assert(revisionInfo.local, `Chromium revision is not downloaded. Run "npm install" or "yarn install"`); - chromeExecutable = revisionInfo.executablePath; + const {missingText, executablePath} = resolveExecutablePath(); + if (missingText) + throw new Error(missingText); + chromeExecutable = executablePath; } const usePipe = chromeArguments.includes('--remote-debugging-pipe'); @@ -252,9 +252,7 @@ class Launcher { * @return {string} */ static executablePath() { - const browserFetcher = new BrowserFetcher(); - const revisionInfo = browserFetcher.revisionInfo(ChromiumRevision); - return revisionInfo.executablePath; + return resolveExecutablePath().executablePath; } /** @@ -330,6 +328,27 @@ function waitForWSEndpoint(chromeProcess, timeout) { }); } +/** + * @return {{executablePath: string, missingText: ?string}} + */ +function resolveExecutablePath() { + const executablePath = helper.getEnv('PUPPETEER_EXECUTABLE_PATH'); + if (executablePath) { + const missingText = !fs.existsSync(executablePath) ? 'Tried to use PUPPETEER_EXECUTABLE_PATH env variable to launch browser but did not find any executable at: ' + executablePath : null; + return { executablePath, missingText }; + } + const browserFetcher = new BrowserFetcher(); + const revision = helper.getEnv('PUPPETEER_CHROMIUM_REVISION'); + if (revision) { + const revisionInfo = browserFetcher.revisionInfo(revision); + const missingText = !revisionInfo.local ? 'Tried to use PUPPETEER_CHROMIUM_REVISION env variable to launch browser but did not find executable at: ' + revisionInfo.executablePath : null; + return {executablePath: revisionInfo.executablePath, missingText}; + } + const revisionInfo = browserFetcher.revisionInfo(ChromiumRevision); + const missingText = !revisionInfo.local ? `Chromium revision is not downloaded. Run "npm install" or "yarn install"` : null; + return {executablePath: revisionInfo.executablePath, missingText}; +} + /** * @typedef {Object} ChromeArgOptions * @property {boolean=} headless diff --git a/lib/helper.js b/lib/helper.js index f5dbf0e3..ddf7de53 100644 --- a/lib/helper.js +++ b/lib/helper.js @@ -20,7 +20,11 @@ const {TimeoutError} = require('./Errors'); const debugError = require('debug')(`puppeteer:error`); /** @type {?Map} */ let apiCoverage = null; -let projectRoot = null; + +// Project root will be different for node6-transpiled code. +const projectRoot = fs.existsSync(path.join(__dirname, '..', 'package.json')) ? path.join(__dirname, '..') : path.join(__dirname, '..', '..'); +const packageJson = require(path.join(projectRoot, 'package.json')); + class Helper { /** * @param {Function|string} fun @@ -45,14 +49,21 @@ class Helper { } } + /** + * @param {string} name + * @return {(string|undefined)} + */ + static getEnv(name) { + // Ignore all PUPPETEER_* env variables in puppeteer-core package. + if (name.startsWith('PUPPETEER_') && packageJson.name === 'puppeteer-core') + return undefined; + return process.env[name]; + } + /** * @return {string} */ static projectRoot() { - if (!projectRoot) { - // Project root will be different for node6-transpiled code. - projectRoot = fs.existsSync(path.join(__dirname, '..', 'package.json')) ? path.join(__dirname, '..') : path.join(__dirname, '..', '..'); - } return projectRoot; }