feat: add channel parameter for puppeteer.launch (#7389)

This change adds a new `channel` parameter to `puppeteer.launch`. When specified, Puppeteer will search for the locally installed release channel of Google Chrome and use it to launch. Available values are `chrome`, `chrome-beta`, `chrome-canary`, `chrome-dev`. This parameter is mutually exclusive with `executablePath`.
This commit is contained in:
Yusuke Iwaki 2021-07-09 21:43:42 +09:00 committed by GitHub
parent d541e975ae
commit d70f60e061
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 115 additions and 8 deletions

View File

@ -454,7 +454,7 @@ When using `puppeteer-core`, remember to change the _include_ line:
const puppeteer = require('puppeteer-core'); const puppeteer = require('puppeteer-core');
``` ```
You will then need to call [`puppeteer.connect([options])`](#puppeteerconnectoptions) or [`puppeteer.launch([options])`](#puppeteerlaunchoptions) with an explicit `executablePath` option. You will then need to call [`puppeteer.connect([options])`](#puppeteerconnectoptions) or [`puppeteer.launch([options])`](#puppeteerlaunchoptions) with an explicit `executablePath` or `channel` option.
### Environment Variables ### Environment Variables
@ -627,6 +627,7 @@ try {
- `product` <[string]> Which browser to launch. At this time, this is either `chrome` or `firefox`. See also `PUPPETEER_PRODUCT`. - `product` <[string]> Which browser to launch. At this time, this is either `chrome` or `firefox`. See also `PUPPETEER_PRODUCT`.
- `ignoreHTTPSErrors` <[boolean]> Whether to ignore HTTPS errors during navigation. Defaults to `false`. - `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`. - `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`.
- `channel` <[string]> When specified, Puppeteer will search for the locally installed release channel of Google Chrome and use it to launch. Available values are `chrome`, `chrome-beta`, `chrome-canary`, `chrome-dev`. When channel is specified, `executablePath` cannot be specified.
- `executablePath` <[string]> Path to a browser 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/puppeteer/puppeteer/#q-why-doesnt-puppeteer-vxxx-work-with-chromium-vyyy) with the bundled Chromium, use at your own risk. - `executablePath` <[string]> Path to a browser 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/puppeteer/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. - `slowMo` <[number]> Slows down Puppeteer operations by the specified amount of milliseconds. Useful so that you can see what is going on.
- `defaultViewport` <?[Object]> Sets a consistent viewport for each page. Defaults to an 800x600 viewport. `null` disables the default viewport. - `defaultViewport` <?[Object]> Sets a consistent viewport for each page. Defaults to an 800x600 viewport. `null` disables the default viewport.
@ -660,7 +661,7 @@ const browser = await puppeteer.launch({
}); });
``` ```
> **NOTE** Puppeteer can also be used to control the Chrome browser, but it works best with the version of Chromium it is bundled with. There is no guarantee it will work with any other version. Use `executablePath` option with extreme caution. > **NOTE** Puppeteer can also be used to control the Chrome browser, but it works best with the version of Chromium it is bundled with. There is no guarantee it will work with any other version. Use `executablePath` or `channel` option with extreme caution.
> >
> If Google Chrome (rather than Chromium) is preferred, a [Chrome Canary](https://www.google.com/chrome/browser/canary.html) or [Dev Channel](https://www.chromium.org/getting-involved/dev-channel) build is suggested. > If Google Chrome (rather than Chromium) is preferred, a [Chrome Canary](https://www.google.com/chrome/browser/canary.html) or [Dev Channel](https://www.chromium.org/getting-involved/dev-channel) build is suggested.
> >

View File

@ -46,11 +46,21 @@ export interface BrowserLaunchArgumentOptions {
args?: string[]; args?: string[];
} }
export type ChromeReleaseChannel =
| 'chrome'
| 'chrome-beta'
| 'chrome-canary'
| 'chrome-dev';
/** /**
* Generic launch options that can be passed when launching any browser. * Generic launch options that can be passed when launching any browser.
* @public * @public
*/ */
export interface LaunchOptions { export interface LaunchOptions {
/**
* Chrome Release Channel
*/
channel?: ChromeReleaseChannel;
/** /**
* Path to a browser executable to use instead of the bundled Chromium. Note * Path to a browser executable to use instead of the bundled Chromium. Note
* that Puppeteer is only guaranteed to work with the bundled Chromium, so use * that Puppeteer is only guaranteed to work with the bundled Chromium, so use

View File

@ -17,6 +17,7 @@ import * as os from 'os';
import * as path from 'path'; import * as path from 'path';
import * as fs from 'fs'; import * as fs from 'fs';
import { assert } from '../common/assert.js';
import { BrowserFetcher } from './BrowserFetcher.js'; import { BrowserFetcher } from './BrowserFetcher.js';
import { Browser } from '../common/Browser.js'; import { Browser } from '../common/Browser.js';
import { BrowserRunner } from './BrowserRunner.js'; import { BrowserRunner } from './BrowserRunner.js';
@ -27,6 +28,7 @@ const writeFileAsync = promisify(fs.writeFile);
import { import {
BrowserLaunchArgumentOptions, BrowserLaunchArgumentOptions,
ChromeReleaseChannel,
PuppeteerNodeLaunchOptions, PuppeteerNodeLaunchOptions,
} from './LaunchOptions.js'; } from './LaunchOptions.js';
import { Product } from '../common/Product.js'; import { Product } from '../common/Product.js';
@ -37,7 +39,7 @@ import { Product } from '../common/Product.js';
*/ */
export interface ProductLauncher { export interface ProductLauncher {
launch(object: PuppeteerNodeLaunchOptions); launch(object: PuppeteerNodeLaunchOptions);
executablePath: () => string; executablePath: (string?) => string;
defaultArgs(object: BrowserLaunchArgumentOptions); defaultArgs(object: BrowserLaunchArgumentOptions);
product: Product; product: Product;
} }
@ -65,6 +67,7 @@ class ChromeLauncher implements ProductLauncher {
ignoreDefaultArgs = false, ignoreDefaultArgs = false,
args = [], args = [],
dumpio = false, dumpio = false,
channel = null,
executablePath = null, executablePath = null,
pipe = false, pipe = false,
env = process.env, env = process.env,
@ -105,7 +108,16 @@ class ChromeLauncher implements ProductLauncher {
} }
let chromeExecutable = executablePath; let chromeExecutable = executablePath;
if (!executablePath) {
if (channel) {
// executablePath is detected by channel, so it should not be specified by user.
assert(
!executablePath,
'`executablePath` must not be specified when `channel` is given.'
);
chromeExecutable = executablePathForChannel(channel);
} else if (!executablePath) {
// Use Intel x86 builds on Apple M1 until native macOS arm64 // Use Intel x86 builds on Apple M1 until native macOS arm64
// Chromium builds are available. // Chromium builds are available.
if (os.platform() !== 'darwin' && os.arch() === 'arm64') { if (os.platform() !== 'darwin' && os.arch() === 'arm64') {
@ -204,8 +216,12 @@ class ChromeLauncher implements ProductLauncher {
return chromeArguments; return chromeArguments;
} }
executablePath(): string { executablePath(channel?: ChromeReleaseChannel): string {
return resolveExecutablePath(this).executablePath; if (channel) {
return executablePathForChannel(channel);
} else {
return resolveExecutablePath(this).executablePath;
}
} }
get product(): Product { get product(): Product {
@ -587,6 +603,80 @@ class FirefoxLauncher implements ProductLauncher {
} }
} }
function executablePathForChannel(channel: ChromeReleaseChannel): string {
const platform = os.platform();
let chromePath: string | undefined;
switch (platform) {
case 'win32':
switch (channel) {
case 'chrome':
chromePath = `${process.env.PROGRAMFILES}\\Google\\Chrome\\Application\\chrome.exe`;
break;
case 'chrome-beta':
chromePath = `${process.env.PROGRAMFILES}\\Google\\Chrome Beta\\Application\\chrome.exe`;
break;
case 'chrome-canary':
chromePath = `${process.env.PROGRAMFILES}\\Google\\Chrome SxS\\Application\\chrome.exe`;
break;
case 'chrome-dev':
chromePath = `${process.env.PROGRAMFILES}\\Google\\Chrome Dev\\Application\\chrome.exe`;
break;
}
break;
case 'darwin':
switch (channel) {
case 'chrome':
chromePath =
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
break;
case 'chrome-beta':
chromePath =
'/Applications/Google Chrome Beta.app/Contents/MacOS/Google Chrome Beta';
break;
case 'chrome-canary':
chromePath =
'/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary';
break;
case 'chrome-dev':
chromePath =
'/Applications/Google Chrome Dev.app/Contents/MacOS/Google Chrome Dev';
break;
}
break;
case 'linux':
switch (channel) {
case 'chrome':
chromePath = '/opt/google/chrome/chrome';
break;
case 'chrome-beta':
chromePath = '/opt/google/chrome-beta/chrome';
break;
case 'chrome-dev':
chromePath = '/opt/google/chrome-unstable/chrome';
break;
}
break;
}
if (!chromePath) {
throw new Error(
`Unable to detect browser executable path for '${channel}' on ${platform}.`
);
}
// Check if Chrome exists and is accessible.
try {
fs.accessSync(chromePath);
} catch (error) {
throw new Error(
`Could not find Google Chrome executable for channel '${channel}' at '${chromePath}'.`
);
}
return chromePath;
}
function resolveExecutablePath(launcher: ChromeLauncher | FirefoxLauncher): { function resolveExecutablePath(launcher: ChromeLauncher | FirefoxLauncher): {
executablePath: string; executablePath: string;
missingText?: string; missingText?: string;

View File

@ -165,8 +165,8 @@ export class PuppeteerNode extends Puppeteer {
* The browser binary might not be there if the download was skipped with * The browser binary might not be there if the download was skipped with
* the `PUPPETEER_SKIP_DOWNLOAD` environment variable. * the `PUPPETEER_SKIP_DOWNLOAD` environment variable.
*/ */
executablePath(): string { executablePath(channel?: string): string {
return this._launcher.executablePath(); return this._launcher.executablePath(channel);
} }
/** /**

View File

@ -660,6 +660,12 @@ describe('Launcher specs', function () {
expect(fs.existsSync(executablePath)).toBe(true); expect(fs.existsSync(executablePath)).toBe(true);
expect(fs.realpathSync(executablePath)).toBe(executablePath); expect(fs.realpathSync(executablePath)).toBe(executablePath);
}); });
it('returns executablePath for channel', () => {
const { puppeteer } = getTestState();
const executablePath = puppeteer.executablePath('chrome');
expect(executablePath).toBeTruthy();
});
}); });
}); });