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:
jrandolf
2022-10-21 15:09:21 +02:00
committed by GitHub
parent efcbc97c60
commit ec201744f0
55 changed files with 925 additions and 706 deletions

View File

@@ -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;
};
}

View File

@@ -107,7 +107,7 @@ export class Puppeteer {
/**
* @internal
*/
protected _isPuppeteerCore: boolean;
_isPuppeteerCore: boolean;
/**
* @internal
*/

View File

@@ -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}`);
}
}

View File

@@ -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;
}
}

View File

@@ -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);

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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();
};

View File

@@ -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';

View File

@@ -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();

View File

@@ -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",

View 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;
};

View File

@@ -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;

View File

@@ -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 {

View File

@@ -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';