mirror of
https://github.com/puppeteer/puppeteer
synced 2024-06-14 14:02:48 +00:00
feat: use configuration files (#9140)
This PR adds configurations files to `puppeteer`'s methods for configuration. Under the hood, `puppeteer` relies on https://www.npmjs.com/package/cosmiconfig which resolves several formats of configuration: - a `puppeteer` property in package.json - a `.puppeteerrc` file in JSON or YAML format - a `.puppeteerrc.json`, `.puppeteerrc.yaml`, `.puppeteerrc.yml`, `.puppeteerrc.js`, or `.puppeteerrc.cjs` file - a `puppeteer.config.js` or `puppeteer.config.cjs` CommonJS module exporting an object Documentation will be added later. Fixed: #9128
This commit is contained in:
@@ -14,11 +14,22 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {homedir} from 'os';
|
||||
import {join} from 'path';
|
||||
import {Product} from './Product.js';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @public
|
||||
*/
|
||||
export const PUPPETEER_CACHE_DIR =
|
||||
process.env['PUPPETEER_CACHE_DIR'] ?? join(homedir(), '.cache', 'puppeteer');
|
||||
export interface Configuration {
|
||||
browserRevision?: string;
|
||||
cacheDirectory?: string;
|
||||
downloadHost?: string;
|
||||
downloadPath?: string;
|
||||
executablePath?: string;
|
||||
defaultProduct?: Product;
|
||||
temporaryDirectory?: string;
|
||||
skipDownload?: boolean;
|
||||
logLevel?: 'silent' | 'error' | 'warn';
|
||||
experiments?: {
|
||||
macArmChromiumEnabled?: boolean;
|
||||
};
|
||||
}
|
||||
@@ -107,7 +107,7 @@ export class Puppeteer {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
protected _isPuppeteerCore: boolean;
|
||||
_isPuppeteerCore: boolean;
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
import {exec as execChildProcess} from 'child_process';
|
||||
import extractZip from 'extract-zip';
|
||||
import {createReadStream, createWriteStream, existsSync} from 'fs';
|
||||
import {createReadStream, createWriteStream, existsSync, readdirSync} from 'fs';
|
||||
import {chmod, mkdir, readdir, unlink} from 'fs/promises';
|
||||
import * as http from 'http';
|
||||
import * as https from 'https';
|
||||
@@ -35,13 +35,8 @@ import * as util from 'util';
|
||||
import {promisify} from 'util';
|
||||
import {debug} from '../common/Debug.js';
|
||||
import {Product} from '../common/Product.js';
|
||||
import {PUPPETEER_CACHE_DIR} from '../constants.js';
|
||||
import {assert} from '../util/assert.js';
|
||||
|
||||
const experimentalChromiumMacArm =
|
||||
process.env['PUPPETEER_EXPERIMENTAL_CHROMIUM_MAC_ARM'] ||
|
||||
process.env['npm_config_puppeteer_experimental_chromium_mac_arm'];
|
||||
|
||||
const debugFetcher = debug('puppeteer:fetcher');
|
||||
|
||||
const downloadURLs: Record<Product, Partial<Record<Platform, string>>> = {
|
||||
@@ -140,24 +135,39 @@ function handleArm64(): void {
|
||||
* @public
|
||||
*/
|
||||
export interface BrowserFetcherOptions {
|
||||
/**
|
||||
* Determines the path to download browsers to.
|
||||
*/
|
||||
path: string;
|
||||
/**
|
||||
* Determines which platform the browser will be suited for.
|
||||
*
|
||||
* @defaultValue Auto-detected.
|
||||
*/
|
||||
platform?: Platform;
|
||||
/**
|
||||
* Determines which product the {@link BrowserFetcher} is for.
|
||||
*
|
||||
* @defaultValue `"chrome"`
|
||||
* @defaultValue `"chrome"`.
|
||||
*/
|
||||
product?: 'chrome' | 'firefox';
|
||||
/**
|
||||
* Determines the path to download browsers to.
|
||||
*/
|
||||
path?: string;
|
||||
/**
|
||||
* Determines the host that will be used for downloading.
|
||||
*
|
||||
* @defaultValue Either
|
||||
*
|
||||
* - https://storage.googleapis.com or
|
||||
* - https://archive.mozilla.org/pub/firefox/nightly/latest-mozilla-central
|
||||
*
|
||||
*/
|
||||
host?: string;
|
||||
|
||||
/**
|
||||
* Enables the use of the Chromium binary for macOS ARM.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
useMacOSARMBinary?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -196,7 +206,7 @@ export interface BrowserFetcherRevisionInfo {
|
||||
* and running Puppeteer against it:
|
||||
*
|
||||
* ```ts
|
||||
* const browserFetcher = new BrowserFetcher();
|
||||
* const browserFetcher = new BrowserFetcher({path: 'path/to/download/folder'});
|
||||
* const revisionInfo = await browserFetcher.download('533271');
|
||||
* const browser = await puppeteer.launch({
|
||||
* executablePath: revisionInfo.executablePath,
|
||||
@@ -208,23 +218,17 @@ export interface BrowserFetcherRevisionInfo {
|
||||
|
||||
export class BrowserFetcher {
|
||||
#product: Product;
|
||||
#downloadFolder: string;
|
||||
#downloadPath: string;
|
||||
#downloadHost: string;
|
||||
#platform: Platform;
|
||||
|
||||
/**
|
||||
* Constructs a browser fetcher for the given options.
|
||||
*/
|
||||
constructor(options: BrowserFetcherOptions = {}) {
|
||||
this.#product = (options.product || 'chrome').toLowerCase() as Product;
|
||||
assert(
|
||||
this.#product === 'chrome' || this.#product === 'firefox',
|
||||
`Unknown product: "${options.product}"`
|
||||
);
|
||||
|
||||
this.#downloadFolder =
|
||||
options.path || path.join(PUPPETEER_CACHE_DIR, this.#product);
|
||||
this.#downloadHost = options.host || browserConfig[this.#product].host;
|
||||
constructor(options: BrowserFetcherOptions) {
|
||||
this.#product = options.product ?? 'chrome';
|
||||
this.#downloadPath = options.path;
|
||||
this.#downloadHost = options.host ?? browserConfig[this.#product].host;
|
||||
|
||||
if (options.platform) {
|
||||
this.#platform = options.platform;
|
||||
@@ -235,7 +239,7 @@ export class BrowserFetcher {
|
||||
switch (this.#product) {
|
||||
case 'chrome':
|
||||
this.#platform =
|
||||
os.arch() === 'arm64' && experimentalChromiumMacArm
|
||||
os.arch() === 'arm64' && options.useMacOSARMBinary
|
||||
? 'mac_arm'
|
||||
: 'mac';
|
||||
break;
|
||||
@@ -342,13 +346,13 @@ export class BrowserFetcher {
|
||||
);
|
||||
const fileName = url.split('/').pop();
|
||||
assert(fileName, `A malformed download URL was found: ${url}.`);
|
||||
const archivePath = path.join(this.#downloadFolder, fileName);
|
||||
const archivePath = path.join(this.#downloadPath, fileName);
|
||||
const outputPath = this.#getFolderPath(revision);
|
||||
if (existsSync(outputPath)) {
|
||||
return this.revisionInfo(revision);
|
||||
}
|
||||
if (!existsSync(this.#downloadFolder)) {
|
||||
await mkdir(this.#downloadFolder, {recursive: true});
|
||||
if (!existsSync(this.#downloadPath)) {
|
||||
await mkdir(this.#downloadPath, {recursive: true});
|
||||
}
|
||||
|
||||
// Use system Chromium builds on Linux ARM devices
|
||||
@@ -374,25 +378,21 @@ export class BrowserFetcher {
|
||||
/**
|
||||
* @remarks
|
||||
* This method is affected by the current `product`.
|
||||
* @returns A promise with a list of all revision strings (for the current `product`)
|
||||
* @returns A list of all revision strings (for the current `product`)
|
||||
* available locally on disk.
|
||||
*/
|
||||
async localRevisions(): Promise<string[]> {
|
||||
if (!existsSync(this.#downloadFolder)) {
|
||||
localRevisions(): string[] {
|
||||
if (!existsSync(this.#downloadPath)) {
|
||||
return [];
|
||||
}
|
||||
const fileNames = await readdir(this.#downloadFolder);
|
||||
const fileNames = readdirSync(this.#downloadPath);
|
||||
return fileNames
|
||||
.map(fileName => {
|
||||
return parseFolderPath(this.#product, fileName);
|
||||
})
|
||||
.filter(
|
||||
(
|
||||
entry
|
||||
): entry is {product: string; platform: string; revision: string} => {
|
||||
return (entry && entry.platform === this.#platform) ?? false;
|
||||
}
|
||||
)
|
||||
.filter((entry): entry is Exclude<typeof entry, undefined> => {
|
||||
return (entry && entry.platform === this.#platform) ?? false;
|
||||
})
|
||||
.map(entry => {
|
||||
return entry.revision;
|
||||
});
|
||||
@@ -502,7 +502,7 @@ export class BrowserFetcher {
|
||||
}
|
||||
|
||||
#getFolderPath(revision: string): string {
|
||||
return path.resolve(this.#downloadFolder, `${this.#platform}-${revision}`);
|
||||
return path.resolve(this.#downloadPath, `${this.#platform}-${revision}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import fs from 'fs';
|
||||
import {accessSync} from 'fs';
|
||||
import {mkdtemp} from 'fs/promises';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
import {CDPBrowser} from '../common/Browser.js';
|
||||
import {Product} from '../common/Product.js';
|
||||
import {assert} from '../util/assert.js';
|
||||
import {BrowserRunner} from './BrowserRunner.js';
|
||||
import {
|
||||
@@ -9,32 +10,20 @@ import {
|
||||
ChromeReleaseChannel,
|
||||
PuppeteerNodeLaunchOptions,
|
||||
} from './LaunchOptions.js';
|
||||
import {
|
||||
executablePathForChannel,
|
||||
ProductLauncher,
|
||||
resolveExecutablePath,
|
||||
} from './ProductLauncher.js';
|
||||
import {tmpdir} from './util.js';
|
||||
import {ProductLauncher} from './ProductLauncher.js';
|
||||
import {PuppeteerNode} from './PuppeteerNode.js';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export class ChromeLauncher implements ProductLauncher {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
_preferredRevision: string;
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
_isPuppeteerCore: boolean;
|
||||
|
||||
constructor(preferredRevision: string, isPuppeteerCore: boolean) {
|
||||
this._preferredRevision = preferredRevision;
|
||||
this._isPuppeteerCore = isPuppeteerCore;
|
||||
export class ChromeLauncher extends ProductLauncher {
|
||||
constructor(puppeteer: PuppeteerNode) {
|
||||
super(puppeteer, 'chrome');
|
||||
}
|
||||
|
||||
async launch(options: PuppeteerNodeLaunchOptions = {}): Promise<CDPBrowser> {
|
||||
override async launch(
|
||||
options: PuppeteerNodeLaunchOptions = {}
|
||||
): Promise<CDPBrowser> {
|
||||
const {
|
||||
ignoreDefaultArgs = false,
|
||||
args = [],
|
||||
@@ -93,9 +82,7 @@ export class ChromeLauncher implements ProductLauncher {
|
||||
if (userDataDirIndex < 0) {
|
||||
isTempUserDataDir = true;
|
||||
chromeArguments.push(
|
||||
`--user-data-dir=${await fs.promises.mkdtemp(
|
||||
path.join(tmpdir(), 'puppeteer_dev_chrome_profile-')
|
||||
)}`
|
||||
`--user-data-dir=${await mkdtemp(this.getProfilePath())}`
|
||||
);
|
||||
userDataDirIndex = chromeArguments.length - 1;
|
||||
}
|
||||
@@ -104,20 +91,12 @@ export class ChromeLauncher implements ProductLauncher {
|
||||
assert(typeof userDataDir === 'string', '`--user-data-dir` is malformed');
|
||||
|
||||
let chromeExecutable = executablePath;
|
||||
if (channel) {
|
||||
// executablePath is detected by channel, so it should not be specified by user.
|
||||
if (!chromeExecutable) {
|
||||
assert(
|
||||
!chromeExecutable,
|
||||
'`executablePath` must not be specified when `channel` is given.'
|
||||
channel || !this.puppeteer._isPuppeteerCore,
|
||||
`An \`executablePath\` or \`channel\` must be specified for \`puppeteer-core\``
|
||||
);
|
||||
|
||||
chromeExecutable = executablePathForChannel(channel);
|
||||
} else if (!chromeExecutable) {
|
||||
const {missingText, executablePath} = resolveExecutablePath(this);
|
||||
if (missingText) {
|
||||
throw new Error(missingText);
|
||||
}
|
||||
chromeExecutable = executablePath;
|
||||
chromeExecutable = this.executablePath(channel);
|
||||
}
|
||||
|
||||
const usePipe = chromeArguments.includes('--remote-debugging-pipe');
|
||||
@@ -143,7 +122,7 @@ export class ChromeLauncher implements ProductLauncher {
|
||||
usePipe,
|
||||
timeout,
|
||||
slowMo,
|
||||
preferredRevision: this._preferredRevision,
|
||||
preferredRevision: this.puppeteer.browserRevision,
|
||||
});
|
||||
browser = await CDPBrowser._create(
|
||||
this.product,
|
||||
@@ -177,7 +156,7 @@ export class ChromeLauncher implements ProductLauncher {
|
||||
return browser;
|
||||
}
|
||||
|
||||
defaultArgs(options: BrowserLaunchArgumentOptions = {}): string[] {
|
||||
override defaultArgs(options: BrowserLaunchArgumentOptions = {}): string[] {
|
||||
const chromeArguments = [
|
||||
'--allow-pre-commit-input',
|
||||
'--disable-background-networking',
|
||||
@@ -241,16 +220,88 @@ export class ChromeLauncher implements ProductLauncher {
|
||||
return chromeArguments;
|
||||
}
|
||||
|
||||
executablePath(channel?: ChromeReleaseChannel): string {
|
||||
override executablePath(channel?: ChromeReleaseChannel): string {
|
||||
if (channel) {
|
||||
return executablePathForChannel(channel);
|
||||
return this.#executablePathForChannel(channel);
|
||||
} else {
|
||||
const results = resolveExecutablePath(this);
|
||||
return results.executablePath;
|
||||
return this.resolveExecutablePath();
|
||||
}
|
||||
}
|
||||
|
||||
get product(): Product {
|
||||
return 'chrome';
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
#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 {
|
||||
accessSync(chromePath);
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Could not find Google Chrome executable for channel '${channel}' at '${chromePath}'.`
|
||||
);
|
||||
}
|
||||
|
||||
return chromePath;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import path from 'path';
|
||||
import {Browser} from '../api/Browser.js';
|
||||
import {Browser as BiDiBrowser} from '../common/bidi/Browser.js';
|
||||
import {CDPBrowser} from '../common/Browser.js';
|
||||
import {Product} from '../common/Product.js';
|
||||
import {assert} from '../util/assert.js';
|
||||
import {BrowserFetcher} from './BrowserFetcher.js';
|
||||
import {BrowserRunner} from './BrowserRunner.js';
|
||||
@@ -12,33 +11,25 @@ import {
|
||||
BrowserLaunchArgumentOptions,
|
||||
PuppeteerNodeLaunchOptions,
|
||||
} from './LaunchOptions.js';
|
||||
import {ProductLauncher, resolveExecutablePath} from './ProductLauncher.js';
|
||||
import {tmpdir} from './util.js';
|
||||
import {ProductLauncher} from './ProductLauncher.js';
|
||||
import {PuppeteerNode} from './PuppeteerNode.js';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export class FirefoxLauncher implements ProductLauncher {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
_preferredRevision: string;
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
_isPuppeteerCore: boolean;
|
||||
|
||||
constructor(preferredRevision: string, isPuppeteerCore: boolean) {
|
||||
this._preferredRevision = preferredRevision;
|
||||
this._isPuppeteerCore = isPuppeteerCore;
|
||||
export class FirefoxLauncher extends ProductLauncher {
|
||||
constructor(puppeteer: PuppeteerNode) {
|
||||
super(puppeteer, 'firefox');
|
||||
}
|
||||
|
||||
async launch(options: PuppeteerNodeLaunchOptions = {}): Promise<Browser> {
|
||||
override async launch(
|
||||
options: PuppeteerNodeLaunchOptions = {}
|
||||
): Promise<Browser> {
|
||||
const {
|
||||
ignoreDefaultArgs = false,
|
||||
args = [],
|
||||
dumpio = false,
|
||||
executablePath = null,
|
||||
executablePath,
|
||||
pipe = false,
|
||||
env = process.env,
|
||||
handleSIGINT = true,
|
||||
@@ -107,20 +98,15 @@ export class FirefoxLauncher implements ProductLauncher {
|
||||
firefoxArguments.push(userDataDir);
|
||||
}
|
||||
|
||||
if (!this._isPuppeteerCore) {
|
||||
await this._updateRevision();
|
||||
}
|
||||
let firefoxExecutable = executablePath;
|
||||
if (!executablePath) {
|
||||
const {missingText, executablePath} = resolveExecutablePath(this);
|
||||
if (missingText) {
|
||||
throw new Error(missingText);
|
||||
}
|
||||
let firefoxExecutable: string;
|
||||
if (this.puppeteer._isPuppeteerCore || executablePath) {
|
||||
assert(
|
||||
executablePath,
|
||||
`An \`executablePath\` must be specified for \`puppeteer-core\``
|
||||
);
|
||||
firefoxExecutable = executablePath;
|
||||
}
|
||||
|
||||
if (!firefoxExecutable) {
|
||||
throw new Error('firefoxExecutable is not found.');
|
||||
} else {
|
||||
firefoxExecutable = this.executablePath();
|
||||
}
|
||||
|
||||
const runner = new BrowserRunner(
|
||||
@@ -145,7 +131,7 @@ export class FirefoxLauncher implements ProductLauncher {
|
||||
const connection = await runner.setupWebDriverBiDiConnection({
|
||||
timeout,
|
||||
slowMo,
|
||||
preferredRevision: this._preferredRevision,
|
||||
preferredRevision: this.puppeteer.browserRevision,
|
||||
});
|
||||
browser = await BiDiBrowser.create({
|
||||
connection,
|
||||
@@ -166,7 +152,7 @@ export class FirefoxLauncher implements ProductLauncher {
|
||||
usePipe: pipe,
|
||||
timeout,
|
||||
slowMo,
|
||||
preferredRevision: this._preferredRevision,
|
||||
preferredRevision: this.puppeteer.browserRevision,
|
||||
});
|
||||
browser = await CDPBrowser._create(
|
||||
this.product,
|
||||
@@ -200,28 +186,22 @@ export class FirefoxLauncher implements ProductLauncher {
|
||||
return browser;
|
||||
}
|
||||
|
||||
executablePath(): string {
|
||||
return resolveExecutablePath(this).executablePath;
|
||||
}
|
||||
|
||||
async _updateRevision(): Promise<void> {
|
||||
override executablePath(): string {
|
||||
// replace 'latest' placeholder with actual downloaded revision
|
||||
if (this._preferredRevision === 'latest') {
|
||||
if (this.puppeteer.browserRevision === 'latest') {
|
||||
const browserFetcher = new BrowserFetcher({
|
||||
product: this.product,
|
||||
path: this.puppeteer.defaultDownloadPath!,
|
||||
});
|
||||
const localRevisions = await browserFetcher.localRevisions();
|
||||
const localRevisions = browserFetcher.localRevisions();
|
||||
if (localRevisions[0]) {
|
||||
this._preferredRevision = localRevisions[0];
|
||||
this.puppeteer.configuration.browserRevision = localRevisions[0];
|
||||
}
|
||||
}
|
||||
return this.resolveExecutablePath();
|
||||
}
|
||||
|
||||
get product(): Product {
|
||||
return 'firefox';
|
||||
}
|
||||
|
||||
defaultArgs(options: BrowserLaunchArgumentOptions = {}): string[] {
|
||||
override defaultArgs(options: BrowserLaunchArgumentOptions = {}): string[] {
|
||||
const {
|
||||
devtools = false,
|
||||
headless = !devtools,
|
||||
@@ -503,7 +483,7 @@ export class FirefoxLauncher implements ProductLauncher {
|
||||
|
||||
async _createProfile(extraPrefs: {[x: string]: unknown}): Promise<string> {
|
||||
const temporaryProfilePath = await fs.promises.mkdtemp(
|
||||
path.join(tmpdir(), 'puppeteer_dev_firefox_profile-')
|
||||
this.getProfilePath()
|
||||
);
|
||||
|
||||
const prefs = this.defaultPreferences(extraPrefs);
|
||||
|
||||
@@ -13,192 +13,118 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import {accessSync, existsSync} from 'fs';
|
||||
import os from 'os';
|
||||
import {existsSync} from 'fs';
|
||||
import os, {tmpdir} from 'os';
|
||||
import {join} from 'path';
|
||||
import {Browser} from '../api/Browser.js';
|
||||
import {Product} from '../common/Product.js';
|
||||
import {BrowserFetcher} from './BrowserFetcher.js';
|
||||
import {ChromeLauncher} from './ChromeLauncher.js';
|
||||
import {FirefoxLauncher} from './FirefoxLauncher.js';
|
||||
import {
|
||||
BrowserLaunchArgumentOptions,
|
||||
ChromeReleaseChannel,
|
||||
PuppeteerNodeLaunchOptions,
|
||||
} from './LaunchOptions.js';
|
||||
import {PuppeteerNode} from './PuppeteerNode.js';
|
||||
|
||||
/**
|
||||
* Describes a launcher - a class that is able to create and launch a browser instance.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface ProductLauncher {
|
||||
export class ProductLauncher {
|
||||
#product: Product;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
puppeteer: PuppeteerNode;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
constructor(puppeteer: PuppeteerNode, product: Product) {
|
||||
this.puppeteer = puppeteer;
|
||||
this.#product = product;
|
||||
}
|
||||
|
||||
get product(): Product {
|
||||
return this.#product;
|
||||
}
|
||||
|
||||
launch(object: PuppeteerNodeLaunchOptions): Promise<Browser>;
|
||||
executablePath: (path?: any) => string;
|
||||
launch(): Promise<Browser> {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
executablePath(channel?: ChromeReleaseChannel): string;
|
||||
executablePath(): string {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
defaultArgs(object: BrowserLaunchArgumentOptions): string[];
|
||||
product: Product;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export 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;
|
||||
defaultArgs(): string[] {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
if (!chromePath) {
|
||||
throw new Error(
|
||||
`Unable to detect browser executable path for '${channel}' on ${platform}.`
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
protected getProfilePath(): string {
|
||||
return join(
|
||||
this.puppeteer.configuration.temporaryDirectory ?? tmpdir(),
|
||||
`puppeteer_dev_${this.product}_profile-`
|
||||
);
|
||||
}
|
||||
|
||||
// Check if Chrome exists and is accessible.
|
||||
try {
|
||||
accessSync(chromePath);
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Could not find Google Chrome executable for channel '${channel}' at '${chromePath}'.`
|
||||
);
|
||||
}
|
||||
|
||||
return chromePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export function resolveExecutablePath(
|
||||
launcher: ChromeLauncher | FirefoxLauncher
|
||||
): {
|
||||
executablePath: string;
|
||||
missingText?: string;
|
||||
} {
|
||||
const {product, _isPuppeteerCore, _preferredRevision} = launcher;
|
||||
let downloadPath: string | undefined;
|
||||
// puppeteer-core doesn't take into account PUPPETEER_* env variables.
|
||||
if (!_isPuppeteerCore) {
|
||||
const executablePath =
|
||||
process.env['PUPPETEER_EXECUTABLE_PATH'] ||
|
||||
process.env['npm_config_puppeteer_executable_path'] ||
|
||||
process.env['npm_package_config_puppeteer_executable_path'];
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
protected resolveExecutablePath(): string {
|
||||
const executablePath = this.puppeteer.configuration.executablePath;
|
||||
if (executablePath) {
|
||||
const missingText = !existsSync(executablePath)
|
||||
? 'Tried to use PUPPETEER_EXECUTABLE_PATH env variable to launch browser but did not find any executable at: ' +
|
||||
executablePath
|
||||
: undefined;
|
||||
return {executablePath, missingText};
|
||||
if (!existsSync(executablePath)) {
|
||||
throw new Error(
|
||||
`Tried to find the browser at the configured path (${executablePath}), but no executable was found.`
|
||||
);
|
||||
}
|
||||
return executablePath;
|
||||
}
|
||||
|
||||
const ubuntuChromiumPath = '/usr/bin/chromium-browser';
|
||||
if (
|
||||
product === 'chrome' &&
|
||||
this.product === 'chrome' &&
|
||||
os.platform() !== 'darwin' &&
|
||||
os.arch() === 'arm64' &&
|
||||
existsSync(ubuntuChromiumPath)
|
||||
) {
|
||||
return {executablePath: ubuntuChromiumPath, missingText: undefined};
|
||||
return ubuntuChromiumPath;
|
||||
}
|
||||
downloadPath =
|
||||
process.env['PUPPETEER_DOWNLOAD_PATH'] ||
|
||||
process.env['npm_config_puppeteer_download_path'] ||
|
||||
process.env['npm_package_config_puppeteer_download_path'];
|
||||
}
|
||||
const browserFetcher = new BrowserFetcher({
|
||||
product: product,
|
||||
path: downloadPath,
|
||||
});
|
||||
|
||||
if (!_isPuppeteerCore) {
|
||||
let revision = process.env['PUPPETEER_BROWSER_REVISION'];
|
||||
if (product === 'chrome') {
|
||||
revision ??= process.env['PUPPETEER_CHROMIUM_REVISION'];
|
||||
const browserFetcher = new BrowserFetcher({
|
||||
product: this.product,
|
||||
path: this.puppeteer.defaultDownloadPath!,
|
||||
});
|
||||
|
||||
const revisionInfo = browserFetcher.revisionInfo(
|
||||
this.puppeteer.browserRevision
|
||||
);
|
||||
if (!revisionInfo.local) {
|
||||
if (this.puppeteer.configuration.browserRevision) {
|
||||
throw new Error(
|
||||
`Tried to find the browser at the configured path (${revisionInfo.executablePath}) for revision ${this.puppeteer.browserRevision}, but no executable was found.`
|
||||
);
|
||||
}
|
||||
switch (this.product) {
|
||||
case 'chrome':
|
||||
throw new Error(
|
||||
`Run \`npm install\` to download the correct Chromium revision (${this.puppeteer.browserRevision}).`
|
||||
);
|
||||
case 'firefox':
|
||||
throw new Error(
|
||||
`Run \`PUPPETEER_PRODUCT=firefox npm install\` to download a supported Firefox browser binary.`
|
||||
);
|
||||
}
|
||||
}
|
||||
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
|
||||
: undefined;
|
||||
return {executablePath: revisionInfo.executablePath, missingText};
|
||||
}
|
||||
}
|
||||
const revisionInfo = browserFetcher.revisionInfo(_preferredRevision);
|
||||
|
||||
const firefoxHelp = `Run \`PUPPETEER_PRODUCT=firefox npm install\` to download a supported Firefox browser binary.`;
|
||||
const chromeHelp = `Run \`npm install\` to download the correct Chromium revision (${launcher._preferredRevision}).`;
|
||||
const missingText = !revisionInfo.local
|
||||
? `Could not find expected browser (${product}) locally. ${
|
||||
product === 'chrome' ? chromeHelp : firefoxHelp
|
||||
}`
|
||||
: undefined;
|
||||
return {executablePath: revisionInfo.executablePath, missingText};
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export function createLauncher(
|
||||
preferredRevision: string,
|
||||
isPuppeteerCore: boolean,
|
||||
product: Product = 'chrome'
|
||||
): ProductLauncher {
|
||||
switch (product) {
|
||||
case 'firefox':
|
||||
return new FirefoxLauncher(preferredRevision, isPuppeteerCore);
|
||||
case 'chrome':
|
||||
return new ChromeLauncher(preferredRevision, isPuppeteerCore);
|
||||
default:
|
||||
throw new Error(`Unknown product: ${product}`);
|
||||
return revisionInfo.executablePath;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {join} from 'path';
|
||||
import {Browser} from '../api/Browser.js';
|
||||
import {BrowserConnectOptions} from '../common/BrowserConnector.js';
|
||||
import {Product} from '../common/Product.js';
|
||||
@@ -22,10 +23,17 @@ import {
|
||||
ConnectOptions,
|
||||
Puppeteer,
|
||||
} from '../common/Puppeteer.js';
|
||||
import {Configuration} from '../common/Configuration.js';
|
||||
import {PUPPETEER_REVISIONS} from '../revisions.js';
|
||||
import {BrowserFetcher, BrowserFetcherOptions} from './BrowserFetcher.js';
|
||||
import {BrowserLaunchArgumentOptions, LaunchOptions} from './LaunchOptions.js';
|
||||
import {createLauncher, ProductLauncher} from './ProductLauncher.js';
|
||||
import {ChromeLauncher} from './ChromeLauncher.js';
|
||||
import {FirefoxLauncher} from './FirefoxLauncher.js';
|
||||
import {
|
||||
BrowserLaunchArgumentOptions,
|
||||
ChromeReleaseChannel,
|
||||
LaunchOptions,
|
||||
} from './LaunchOptions.js';
|
||||
import {ProductLauncher} from './ProductLauncher.js';
|
||||
|
||||
/**
|
||||
* @public
|
||||
@@ -74,28 +82,40 @@ export interface PuppeteerLaunchOptions
|
||||
* @public
|
||||
*/
|
||||
export class PuppeteerNode extends Puppeteer {
|
||||
#launcher?: ProductLauncher;
|
||||
#productName?: Product;
|
||||
#_launcher?: ProductLauncher;
|
||||
#lastLaunchedProduct?: Product;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
_preferredRevision = PUPPETEER_REVISIONS.chromium;
|
||||
defaultBrowserRevision: string;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
configuration: Configuration = {};
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
constructor(
|
||||
settings: {
|
||||
preferredRevision?: string;
|
||||
productName?: Product;
|
||||
configuration?: Configuration;
|
||||
} & CommonPuppeteerSettings
|
||||
) {
|
||||
const {preferredRevision, productName, ...commonSettings} = settings;
|
||||
const {configuration, ...commonSettings} = settings;
|
||||
super(commonSettings);
|
||||
this.#productName = productName;
|
||||
if (preferredRevision) {
|
||||
this._preferredRevision = preferredRevision;
|
||||
if (configuration) {
|
||||
this.configuration = configuration;
|
||||
}
|
||||
switch (this.configuration.defaultProduct) {
|
||||
case 'firefox':
|
||||
this.defaultBrowserRevision = PUPPETEER_REVISIONS.firefox;
|
||||
break;
|
||||
default:
|
||||
this.configuration.defaultProduct = 'chrome';
|
||||
this.defaultBrowserRevision = PUPPETEER_REVISIONS.chromium;
|
||||
break;
|
||||
}
|
||||
|
||||
this.connect = this.connect.bind(this);
|
||||
@@ -116,21 +136,8 @@ export class PuppeteerNode extends Puppeteer {
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
get _productName(): Product | undefined {
|
||||
return this.#productName;
|
||||
}
|
||||
set _productName(name: Product | undefined) {
|
||||
if (this.#productName !== name) {
|
||||
this._changedProduct = true;
|
||||
}
|
||||
this.#productName = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Launches puppeteer and launches a browser instance with given arguments and
|
||||
* options when specified.
|
||||
* Launches a browser instance with given arguments and options when
|
||||
* specified.
|
||||
*
|
||||
* @example
|
||||
* You can use `ignoreDefaultArgs` to filter out `--mute-audio` from default arguments:
|
||||
@@ -142,90 +149,118 @@ export class PuppeteerNode extends Puppeteer {
|
||||
* ```
|
||||
*
|
||||
* @remarks
|
||||
* **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. If Google Chrome (rather than Chromium) is preferred,
|
||||
* a {@link https://www.google.com/chrome/browser/canary.html | Chrome Canary}
|
||||
* 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. If Google Chrome (rather than Chromium) is preferred, a
|
||||
* {@link https://www.google.com/chrome/browser/canary.html | Chrome Canary}
|
||||
* or
|
||||
* {@link https://www.chromium.org/getting-involved/dev-channel | Dev Channel}
|
||||
* build is suggested. In `puppeteer.launch([options])`, any mention of
|
||||
* Chromium also applies to Chrome. See
|
||||
* build is suggested. In {@link Puppeteer.launch}, any mention of Chromium
|
||||
* also applies to Chrome. See
|
||||
* {@link https://www.howtogeek.com/202825/what%E2%80%99s-the-difference-between-chromium-and-chrome/ | this article}
|
||||
* for a description of the differences between Chromium and Chrome.
|
||||
* {@link https://chromium.googlesource.com/chromium/src/+/lkgr/docs/chromium_browser_vs_google_chrome.md | This article}
|
||||
* describes some differences for Linux users.
|
||||
*
|
||||
* @param options - Set of configurable options to set on the browser.
|
||||
* @returns Promise which resolves to browser instance.
|
||||
* @param options - Options to configure launching behavior.
|
||||
*/
|
||||
launch(options: PuppeteerLaunchOptions = {}): Promise<Browser> {
|
||||
if (options.product) {
|
||||
this._productName = options.product;
|
||||
}
|
||||
return this._launcher.launch(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @remarks
|
||||
* **NOTE** `puppeteer.executablePath()` is affected by the
|
||||
* `PUPPETEER_EXECUTABLE_PATH` and `PUPPETEER_CHROMIUM_REVISION` environment
|
||||
* variables.
|
||||
*
|
||||
* @returns A path where Puppeteer expects to find the bundled browser. The
|
||||
* browser binary might not be there if the download was skipped with the
|
||||
* `PUPPETEER_SKIP_DOWNLOAD` environment variable.
|
||||
*/
|
||||
executablePath(channel?: string): string {
|
||||
return this._launcher.executablePath(channel);
|
||||
const {product = this.defaultProduct} = options;
|
||||
this.#lastLaunchedProduct = product;
|
||||
return this.#launcher.launch(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
get _launcher(): ProductLauncher {
|
||||
get #launcher(): ProductLauncher {
|
||||
if (
|
||||
!this.#launcher ||
|
||||
this.#launcher.product !== this._productName ||
|
||||
this._changedProduct
|
||||
this.#_launcher &&
|
||||
this.#_launcher.product === this.lastLaunchedProduct
|
||||
) {
|
||||
switch (this._productName) {
|
||||
case 'firefox':
|
||||
this._preferredRevision = PUPPETEER_REVISIONS.firefox;
|
||||
break;
|
||||
case 'chrome':
|
||||
default:
|
||||
this._preferredRevision = PUPPETEER_REVISIONS.chromium;
|
||||
}
|
||||
this._changedProduct = false;
|
||||
this.#launcher = createLauncher(
|
||||
this._preferredRevision,
|
||||
this._isPuppeteerCore,
|
||||
this._productName
|
||||
);
|
||||
return this.#_launcher;
|
||||
}
|
||||
return this.#launcher;
|
||||
switch (this.lastLaunchedProduct) {
|
||||
case 'chrome':
|
||||
this.defaultBrowserRevision = PUPPETEER_REVISIONS.chromium;
|
||||
this.#_launcher = new ChromeLauncher(this);
|
||||
break;
|
||||
case 'firefox':
|
||||
this.defaultBrowserRevision = PUPPETEER_REVISIONS.firefox;
|
||||
this.#_launcher = new FirefoxLauncher(this);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unknown product: ${this.#lastLaunchedProduct}`);
|
||||
}
|
||||
return this.#_launcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the browser that is under automation (`"chrome"` or
|
||||
* `"firefox"`)
|
||||
* @returns The executable path.
|
||||
*/
|
||||
executablePath(channel?: ChromeReleaseChannel): string {
|
||||
return this.#launcher.executablePath(channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
get browserRevision(): string {
|
||||
return this.configuration.browserRevision ?? this.defaultBrowserRevision!;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns The default download path for puppeteer. For puppeteer-core, this
|
||||
* code should never be called as it is never defined.
|
||||
*
|
||||
* @remarks
|
||||
* The product is set by the `PUPPETEER_PRODUCT` environment variable or the
|
||||
* `product` option in `puppeteer.launch([options])` and defaults to `chrome`.
|
||||
* Firefox support is experimental.
|
||||
* @internal
|
||||
*/
|
||||
get defaultDownloadPath(): string | undefined {
|
||||
return (
|
||||
this.configuration.downloadPath ??
|
||||
join(this.configuration.cacheDirectory!, this.product)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns The name of the browser that was last launched.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
get lastLaunchedProduct(): Product {
|
||||
return this.#lastLaunchedProduct ?? this.defaultProduct;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns The name of the browser that will be launched by default. For
|
||||
* `puppeteer`, this is influenced by your configuration. Otherwise, it's
|
||||
* `chrome`.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
get defaultProduct(): Product {
|
||||
return this.configuration.defaultProduct ?? 'chrome';
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Do not use as this field as it does not take into account
|
||||
* multiple browsers of different types. Use {@link defaultProduct} or
|
||||
* {@link lastLaunchedProduct}.
|
||||
*
|
||||
* @returns The name of the browser that is under automation.
|
||||
*/
|
||||
get product(): string {
|
||||
return this._launcher.product;
|
||||
return this.#launcher.product;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param options - Set of configurable options to set on the browser.
|
||||
*
|
||||
* @returns The default flags that Chromium will be launched with.
|
||||
*/
|
||||
defaultArgs(options: BrowserLaunchArgumentOptions = {}): string[] {
|
||||
return this._launcher.defaultArgs(options);
|
||||
return this.#launcher.defaultArgs(options);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -237,7 +272,22 @@ export class PuppeteerNode extends Puppeteer {
|
||||
*
|
||||
* @returns A new BrowserFetcher instance.
|
||||
*/
|
||||
createBrowserFetcher(options: BrowserFetcherOptions): BrowserFetcher {
|
||||
return new BrowserFetcher(options);
|
||||
createBrowserFetcher(
|
||||
options: Partial<BrowserFetcherOptions>
|
||||
): BrowserFetcher {
|
||||
const downloadPath = this.defaultDownloadPath;
|
||||
if (downloadPath) {
|
||||
options.path = downloadPath;
|
||||
}
|
||||
if (!options.path) {
|
||||
throw new Error('A `path` must be specified for `puppeteer-core`.');
|
||||
}
|
||||
if (this.configuration.experiments?.macArmChromiumEnabled) {
|
||||
options.useMacOSARMBinary = true;
|
||||
}
|
||||
if (this.configuration.downloadHost) {
|
||||
options.host = this.configuration.downloadHost;
|
||||
}
|
||||
return new BrowserFetcher(options as BrowserFetcherOptions);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import {tmpdir as osTmpDir} from 'os';
|
||||
|
||||
/**
|
||||
* Gets the temporary directory, either from the environmental variable
|
||||
* `PUPPETEER_TMP_DIR` or the `os.tmpdir`.
|
||||
*
|
||||
* @returns The temporary directory path.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export const tmpdir = (): string => {
|
||||
return process.env['PUPPETEER_TMP_DIR'] || osTmpDir();
|
||||
};
|
||||
@@ -9,6 +9,7 @@ export * from './common/Browser.js';
|
||||
export * from './common/BrowserConnector.js';
|
||||
export * from './common/BrowserWebSocketTransport.js';
|
||||
export * from './common/ChromeTargetManager.js';
|
||||
export * from './common/Configuration.js';
|
||||
export * from './common/Connection.js';
|
||||
export * from './common/ConnectionTransport.js';
|
||||
export * from './common/ConsoleMessage.js';
|
||||
@@ -55,7 +56,6 @@ export * from './common/USKeyboardLayout.js';
|
||||
export * from './common/util.js';
|
||||
export * from './common/WaitTask.js';
|
||||
export * from './common/WebWorker.js';
|
||||
export * from './constants.js';
|
||||
export * from './environment.js';
|
||||
export * from './generated/injected.js';
|
||||
export * from './generated/version.js';
|
||||
@@ -67,7 +67,6 @@ export * from './node/LaunchOptions.js';
|
||||
export * from './node/PipeTransport.js';
|
||||
export * from './node/ProductLauncher.js';
|
||||
export * from './node/PuppeteerNode.js';
|
||||
export * from './node/util.js';
|
||||
export * from './puppeteer-core.js';
|
||||
export * from './revisions.js';
|
||||
export * from './util/assert.js';
|
||||
|
||||
@@ -28,69 +28,13 @@ const path = require('path');
|
||||
const fs = require('fs');
|
||||
const {execSync} = require('child_process');
|
||||
|
||||
async function download() {
|
||||
if (!fs.existsSync(path.join(__dirname, 'lib'))) {
|
||||
console.log('It seems we are installing from the git repo.');
|
||||
console.log('Building install tools from scratch...');
|
||||
execSync('npm run build');
|
||||
}
|
||||
|
||||
// need to ensure TS is compiled before loading the installer
|
||||
const {
|
||||
downloadBrowser,
|
||||
logPolitely,
|
||||
} = require('puppeteer/lib/cjs/puppeteer/node/install.js');
|
||||
|
||||
if (process.env.PUPPETEER_SKIP_DOWNLOAD) {
|
||||
logPolitely(
|
||||
'**INFO** Skipping browser download. "PUPPETEER_SKIP_DOWNLOAD" environment variable was found.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (
|
||||
process.env.NPM_CONFIG_PUPPETEER_SKIP_DOWNLOAD ||
|
||||
process.env.npm_config_puppeteer_skip_download
|
||||
) {
|
||||
logPolitely(
|
||||
'**INFO** Skipping browser download. "PUPPETEER_SKIP_DOWNLOAD" was set in npm config.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (
|
||||
process.env.NPM_PACKAGE_CONFIG_PUPPETEER_SKIP_DOWNLOAD ||
|
||||
process.env.npm_package_config_puppeteer_skip_download
|
||||
) {
|
||||
logPolitely(
|
||||
'**INFO** Skipping browser download. "PUPPETEER_SKIP_DOWNLOAD" was set in project config.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (process.env.PUPPETEER_SKIP_CHROMIUM_DOWNLOAD) {
|
||||
logPolitely(
|
||||
'**INFO** Skipping browser download. "PUPPETEER_SKIP_CHROMIUM_DOWNLOAD" environment variable was found.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (
|
||||
process.env.NPM_CONFIG_PUPPETEER_SKIP_CHROMIUM_DOWNLOAD ||
|
||||
process.env.npm_config_puppeteer_skip_chromium_download
|
||||
) {
|
||||
logPolitely(
|
||||
'**INFO** Skipping browser download. "PUPPETEER_SKIP_CHROMIUM_DOWNLOAD" was set in npm config.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (
|
||||
process.env.NPM_PACKAGE_CONFIG_PUPPETEER_SKIP_CHROMIUM_DOWNLOAD ||
|
||||
process.env.npm_package_config_puppeteer_skip_chromium_download
|
||||
) {
|
||||
logPolitely(
|
||||
'**INFO** Skipping browser download. "PUPPETEER_SKIP_CHROMIUM_DOWNLOAD" was set in project config.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
downloadBrowser();
|
||||
// Need to ensure TS is compiled before loading the installer
|
||||
if (!fs.existsSync(path.join(__dirname, 'lib'))) {
|
||||
console.log('It seems we are installing from the git repo.');
|
||||
console.log('Building install tools from scratch...');
|
||||
execSync('npm run build --workspace puppeteer');
|
||||
}
|
||||
|
||||
download();
|
||||
const {downloadBrowser} = require('puppeteer/internal/node/install.js');
|
||||
|
||||
downloadBrowser();
|
||||
|
||||
@@ -135,6 +135,7 @@
|
||||
"author": "The Chromium Authors",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"cosmiconfig": "7.0.1",
|
||||
"https-proxy-agent": "5.0.1",
|
||||
"progress": "2.0.3",
|
||||
"proxy-from-env": "1.1.0",
|
||||
|
||||
95
packages/puppeteer/src/getConfiguration.ts
Normal file
95
packages/puppeteer/src/getConfiguration.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import {cosmiconfigSync} from 'cosmiconfig';
|
||||
import {homedir} from 'os';
|
||||
import {join} from 'path';
|
||||
import {Configuration, Product} from 'puppeteer-core';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
function isSupportedProduct(product: unknown): product is Product {
|
||||
switch (product) {
|
||||
case 'chrome':
|
||||
case 'firefox':
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export const getConfiguration = (): Configuration => {
|
||||
const result = cosmiconfigSync('puppeteer').search();
|
||||
const configuration: Configuration = result ? result.config : {};
|
||||
|
||||
// Merging environment variables.
|
||||
configuration.browserRevision =
|
||||
process.env['PUPPETEER_CHROMIUM_REVISION'] ??
|
||||
process.env['PUPPETEER_BROWSER_REVISION'] ??
|
||||
process.env['npm_config_puppeteer_browser_revision'] ??
|
||||
process.env['npm_package_config_puppeteer_browser_revision'] ??
|
||||
configuration.browserRevision;
|
||||
configuration.cacheDirectory =
|
||||
process.env['PUPPETEER_CACHE_DIR'] ??
|
||||
process.env['npm_config_puppeteer_cache_dir'] ??
|
||||
process.env['npm_package_config_puppeteer_cache_dir'] ??
|
||||
configuration.cacheDirectory ??
|
||||
join(homedir(), '.cache', 'puppeteer');
|
||||
configuration.downloadHost =
|
||||
process.env['PUPPETEER_DOWNLOAD_HOST'] ??
|
||||
process.env['npm_config_puppeteer_download_host'] ??
|
||||
process.env['npm_package_config_puppeteer_download_host'] ??
|
||||
configuration.downloadHost;
|
||||
configuration.downloadPath =
|
||||
process.env['PUPPETEER_DOWNLOAD_PATH'] ??
|
||||
process.env['npm_config_puppeteer_download_path'] ??
|
||||
process.env['npm_package_config_puppeteer_download_path'] ??
|
||||
configuration.downloadPath;
|
||||
configuration.executablePath =
|
||||
process.env['PUPPETEER_EXECUTABLE_PATH'] ??
|
||||
process.env['npm_config_puppeteer_executable_path'] ??
|
||||
process.env['npm_package_config_puppeteer_executable_path'] ??
|
||||
configuration.executablePath;
|
||||
configuration.defaultProduct = (process.env['PUPPETEER_PRODUCT'] ??
|
||||
process.env['npm_config_puppeteer_product'] ??
|
||||
process.env['npm_package_config_puppeteer_product'] ??
|
||||
configuration.defaultProduct ??
|
||||
'chrome') as Product;
|
||||
configuration.temporaryDirectory =
|
||||
process.env['PUPPETEER_TMP_DIR'] ??
|
||||
process.env['npm_config_puppeteer_tmp_dir'] ??
|
||||
process.env['npm_package_config_puppeteer_tmp_dir'] ??
|
||||
configuration.temporaryDirectory;
|
||||
|
||||
configuration.experiments ??= {};
|
||||
configuration.experiments.macArmChromiumEnabled = Boolean(
|
||||
process.env['PUPPETEER_EXPERIMENTAL_CHROMIUM_MAC_ARM'] ??
|
||||
process.env['npm_config_puppeteer_experimental_chromium_mac_arm'] ??
|
||||
process.env[
|
||||
'npm_package_config_puppeteer_experimental_chromium_mac_arm'
|
||||
] ??
|
||||
configuration.experiments.macArmChromiumEnabled
|
||||
);
|
||||
|
||||
configuration.skipDownload = Boolean(
|
||||
process.env['PUPPETEER_SKIP_DOWNLOAD'] ??
|
||||
process.env['npm_config_puppeteer_skip_download'] ??
|
||||
process.env['npm_package_config_puppeteer_skip_download'] ??
|
||||
process.env['PUPPETEER_SKIP_CHROMIUM_DOWNLOAD'] ??
|
||||
process.env['npm_config_puppeteer_skip_chromium_download'] ??
|
||||
process.env['npm_package_config_puppeteer_skip_chromium_download'] ??
|
||||
configuration.skipDownload
|
||||
);
|
||||
configuration.logLevel = (process.env['PUPPETEER_LOGLEVEL'] ??
|
||||
process.env['npm_config_LOGLEVEL'] ??
|
||||
process.env['npm_package_config_LOGLEVEL'] ??
|
||||
configuration.logLevel) as 'silent' | 'error' | 'warn';
|
||||
|
||||
// Validate configuration.
|
||||
if (!isSupportedProduct(configuration.defaultProduct)) {
|
||||
throw new Error(`Unsupported product ${configuration.defaultProduct}`);
|
||||
}
|
||||
|
||||
return configuration;
|
||||
};
|
||||
@@ -16,13 +16,13 @@
|
||||
|
||||
import https, {RequestOptions} from 'https';
|
||||
import createHttpsProxyAgent, {HttpsProxyAgentOptions} from 'https-proxy-agent';
|
||||
import {join} from 'path';
|
||||
import ProgressBar from 'progress';
|
||||
import {getProxyForUrl} from 'proxy-from-env';
|
||||
import {BrowserFetcher} from 'puppeteer-core';
|
||||
import {PuppeteerNode} from 'puppeteer-core/internal/node/PuppeteerNode.js';
|
||||
import {PUPPETEER_REVISIONS} from 'puppeteer-core/internal/revisions.js';
|
||||
import URL from 'url';
|
||||
import puppeteer from '../puppeteer.js';
|
||||
import {getConfiguration} from '../getConfiguration.js';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@@ -32,61 +32,40 @@ const supportedProducts = {
|
||||
firefox: 'Firefox Nightly',
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
function getProduct(input: string): 'chrome' | 'firefox' {
|
||||
if (input !== 'chrome' && input !== 'firefox') {
|
||||
throw new Error(`Unsupported product ${input}`);
|
||||
}
|
||||
return input;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export async function downloadBrowser(): Promise<void> {
|
||||
const downloadHost =
|
||||
process.env['PUPPETEER_DOWNLOAD_HOST'] ||
|
||||
process.env['npm_config_puppeteer_download_host'] ||
|
||||
process.env['npm_package_config_puppeteer_download_host'];
|
||||
const product = getProduct(
|
||||
process.env['PUPPETEER_PRODUCT'] ||
|
||||
process.env['npm_config_puppeteer_product'] ||
|
||||
process.env['npm_package_config_puppeteer_product'] ||
|
||||
'chrome'
|
||||
);
|
||||
const downloadPath =
|
||||
process.env['PUPPETEER_DOWNLOAD_PATH'] ||
|
||||
process.env['npm_config_puppeteer_download_path'] ||
|
||||
process.env['npm_package_config_puppeteer_download_path'];
|
||||
const configuration = getConfiguration();
|
||||
if (configuration.skipDownload) {
|
||||
logPolitely('**INFO** Skipping browser download as instructed.');
|
||||
}
|
||||
|
||||
const product = configuration.defaultProduct!;
|
||||
const browserFetcher = new BrowserFetcher({
|
||||
product,
|
||||
host: downloadHost,
|
||||
path: downloadPath,
|
||||
host: configuration.downloadHost,
|
||||
path:
|
||||
configuration.downloadPath ??
|
||||
join(configuration.cacheDirectory!, product),
|
||||
});
|
||||
const revision = await getRevision();
|
||||
await fetchBinary(revision);
|
||||
|
||||
async function getRevision(): Promise<string> {
|
||||
if (product === 'chrome') {
|
||||
return (
|
||||
process.env['PUPPETEER_CHROMIUM_REVISION'] ||
|
||||
process.env['npm_config_puppeteer_chromium_revision'] ||
|
||||
PUPPETEER_REVISIONS.chromium
|
||||
);
|
||||
} else if (product === 'firefox') {
|
||||
(puppeteer as PuppeteerNode)._preferredRevision =
|
||||
PUPPETEER_REVISIONS.firefox;
|
||||
return getFirefoxNightlyVersion().catch(error => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
} else {
|
||||
throw new Error(`Unsupported product ${product}`);
|
||||
let revision = configuration.browserRevision;
|
||||
|
||||
if (!revision) {
|
||||
switch (product) {
|
||||
case 'chrome':
|
||||
revision = PUPPETEER_REVISIONS.chromium;
|
||||
break;
|
||||
case 'firefox':
|
||||
revision = PUPPETEER_REVISIONS.firefox;
|
||||
revision = await getFirefoxNightlyVersion();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
await fetchBinary(revision);
|
||||
|
||||
function fetchBinary(revision: string) {
|
||||
const revisionInfo = browserFetcher.revisionInfo(revision);
|
||||
|
||||
@@ -222,7 +201,7 @@ export async function downloadBrowser(): Promise<void> {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export function logPolitely(toBeLogged: unknown): void {
|
||||
function logPolitely(toBeLogged: unknown): void {
|
||||
const logLevel = process.env['npm_config_loglevel'] || '';
|
||||
const logLevelDisplay = ['silent', 'error', 'warn'].indexOf(logLevel) > -1;
|
||||
|
||||
|
||||
@@ -19,37 +19,24 @@ export * from 'puppeteer-core/internal/common/Device.js';
|
||||
export * from 'puppeteer-core/internal/common/Errors.js';
|
||||
export * from 'puppeteer-core/internal/common/PredefinedNetworkConditions.js';
|
||||
export * from 'puppeteer-core/internal/common/Puppeteer.js';
|
||||
export * from 'puppeteer-core/internal/node/BrowserFetcher.js';
|
||||
/**
|
||||
* @deprecated Use the query handler API defined on {@link Puppeteer}
|
||||
*/
|
||||
export * from 'puppeteer-core/internal/common/QueryHandler.js';
|
||||
export * from 'puppeteer-core/internal/node/BrowserFetcher.js';
|
||||
export {LaunchOptions} from 'puppeteer-core/internal/node/LaunchOptions.js';
|
||||
|
||||
import {Product} from 'puppeteer-core';
|
||||
import {PuppeteerNode} from 'puppeteer-core/internal/node/PuppeteerNode.js';
|
||||
import {PUPPETEER_REVISIONS} from 'puppeteer-core/internal/revisions.js';
|
||||
import {getConfiguration} from './getConfiguration.js';
|
||||
|
||||
const productName = (process.env['PUPPETEER_PRODUCT'] ||
|
||||
process.env['npm_config_puppeteer_product'] ||
|
||||
process.env['npm_package_config_puppeteer_product']) as Product;
|
||||
|
||||
let preferredRevision: string;
|
||||
switch (productName) {
|
||||
case 'firefox':
|
||||
preferredRevision = PUPPETEER_REVISIONS.firefox;
|
||||
break;
|
||||
default:
|
||||
preferredRevision = PUPPETEER_REVISIONS.chromium;
|
||||
}
|
||||
const configuration = getConfiguration();
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
const puppeteer = new PuppeteerNode({
|
||||
preferredRevision,
|
||||
isPuppeteerCore: false,
|
||||
productName,
|
||||
configuration,
|
||||
});
|
||||
|
||||
export const {
|
||||
|
||||
@@ -9,6 +9,7 @@ export * from 'puppeteer-core/internal/common/Browser.js';
|
||||
export * from 'puppeteer-core/internal/common/BrowserConnector.js';
|
||||
export * from 'puppeteer-core/internal/common/BrowserWebSocketTransport.js';
|
||||
export * from 'puppeteer-core/internal/common/ChromeTargetManager.js';
|
||||
export * from 'puppeteer-core/internal/common/Configuration.js';
|
||||
export * from 'puppeteer-core/internal/common/Connection.js';
|
||||
export * from 'puppeteer-core/internal/common/ConnectionTransport.js';
|
||||
export * from 'puppeteer-core/internal/common/ConsoleMessage.js';
|
||||
@@ -55,7 +56,6 @@ export * from 'puppeteer-core/internal/common/USKeyboardLayout.js';
|
||||
export * from 'puppeteer-core/internal/common/util.js';
|
||||
export * from 'puppeteer-core/internal/common/WaitTask.js';
|
||||
export * from 'puppeteer-core/internal/common/WebWorker.js';
|
||||
export * from 'puppeteer-core/internal/constants.js';
|
||||
export * from 'puppeteer-core/internal/environment.js';
|
||||
export * from 'puppeteer-core/internal/generated/injected.js';
|
||||
export * from 'puppeteer-core/internal/generated/version.js';
|
||||
@@ -67,10 +67,10 @@ export * from 'puppeteer-core/internal/node/LaunchOptions.js';
|
||||
export * from 'puppeteer-core/internal/node/PipeTransport.js';
|
||||
export * from 'puppeteer-core/internal/node/ProductLauncher.js';
|
||||
export * from 'puppeteer-core/internal/node/PuppeteerNode.js';
|
||||
export * from 'puppeteer-core/internal/node/util.js';
|
||||
export * from 'puppeteer-core/internal/revisions.js';
|
||||
export * from 'puppeteer-core/internal/util/assert.js';
|
||||
export * from 'puppeteer-core/internal/util/DebuggableDeferredPromise.js';
|
||||
export * from 'puppeteer-core/internal/util/DeferredPromise.js';
|
||||
export * from 'puppeteer-core/internal/util/ErrorLike.js';
|
||||
export * from './getConfiguration.js';
|
||||
export * from './puppeteer.js';
|
||||
|
||||
Reference in New Issue
Block a user