Skip to content

Commit

Permalink
feat: add channel parameter for puppeteer.launch (#7389)
Browse files Browse the repository at this point in the history
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`.
  • Loading branch information
Yusuke Iwaki committed Jul 9, 2021
1 parent d541e97 commit d70f60e
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 8 deletions.
5 changes: 3 additions & 2 deletions docs/api.md
Expand Up @@ -454,7 +454,7 @@ When using `puppeteer-core`, remember to change the _include_ line:
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

Expand Down Expand Up @@ -627,6 +627,7 @@ try {
- `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`.
- `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.
- `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.
Expand Down Expand Up @@ -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.
>
Expand Down
10 changes: 10 additions & 0 deletions src/node/LaunchOptions.ts
Expand Up @@ -46,11 +46,21 @@ export interface BrowserLaunchArgumentOptions {
args?: string[];
}

export type ChromeReleaseChannel =
| 'chrome'
| 'chrome-beta'
| 'chrome-canary'
| 'chrome-dev';

/**
* Generic launch options that can be passed when launching any browser.
* @public
*/
export interface LaunchOptions {
/**
* Chrome Release Channel
*/
channel?: ChromeReleaseChannel;
/**
* 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
Expand Down
98 changes: 94 additions & 4 deletions src/node/Launcher.ts
Expand Up @@ -17,6 +17,7 @@ import * as os from 'os';
import * as path from 'path';
import * as fs from 'fs';

import { assert } from '../common/assert.js';
import { BrowserFetcher } from './BrowserFetcher.js';
import { Browser } from '../common/Browser.js';
import { BrowserRunner } from './BrowserRunner.js';
Expand All @@ -27,6 +28,7 @@ const writeFileAsync = promisify(fs.writeFile);

import {
BrowserLaunchArgumentOptions,
ChromeReleaseChannel,
PuppeteerNodeLaunchOptions,
} from './LaunchOptions.js';
import { Product } from '../common/Product.js';
Expand All @@ -37,7 +39,7 @@ import { Product } from '../common/Product.js';
*/
export interface ProductLauncher {
launch(object: PuppeteerNodeLaunchOptions);
executablePath: () => string;
executablePath: (string?) => string;
defaultArgs(object: BrowserLaunchArgumentOptions);
product: Product;
}
Expand Down Expand Up @@ -65,6 +67,7 @@ class ChromeLauncher implements ProductLauncher {
ignoreDefaultArgs = false,
args = [],
dumpio = false,
channel = null,
executablePath = null,
pipe = false,
env = process.env,
Expand Down Expand Up @@ -105,7 +108,16 @@ class ChromeLauncher implements ProductLauncher {
}

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
// Chromium builds are available.
if (os.platform() !== 'darwin' && os.arch() === 'arm64') {
Expand Down Expand Up @@ -204,8 +216,12 @@ class ChromeLauncher implements ProductLauncher {
return chromeArguments;
}

executablePath(): string {
return resolveExecutablePath(this).executablePath;
executablePath(channel?: ChromeReleaseChannel): string {
if (channel) {
return executablePathForChannel(channel);
} else {
return resolveExecutablePath(this).executablePath;
}
}

get product(): Product {
Expand Down Expand Up @@ -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): {
executablePath: string;
missingText?: string;
Expand Down
4 changes: 2 additions & 2 deletions src/node/Puppeteer.ts
Expand Up @@ -165,8 +165,8 @@ export class PuppeteerNode extends Puppeteer {
* The browser binary might not be there if the download was skipped with
* the `PUPPETEER_SKIP_DOWNLOAD` environment variable.
*/
executablePath(): string {
return this._launcher.executablePath();
executablePath(channel?: string): string {
return this._launcher.executablePath(channel);
}

/**
Expand Down
6 changes: 6 additions & 0 deletions test/launcher.spec.ts
Expand Up @@ -660,6 +660,12 @@ describe('Launcher specs', function () {
expect(fs.existsSync(executablePath)).toBe(true);
expect(fs.realpathSync(executablePath)).toBe(executablePath);
});
it('returns executablePath for channel', () => {
const { puppeteer } = getTestState();

const executablePath = puppeteer.executablePath('chrome');
expect(executablePath).toBeTruthy();
});
});
});

Expand Down

0 comments on commit d70f60e

Please sign in to comment.