diff --git a/package.json b/package.json index 0ff21ad885367..8a050e7171513 100644 --- a/package.json +++ b/package.json @@ -80,6 +80,7 @@ "@types/mime": "2.0.3", "@types/mocha": "9.0.0", "@types/node": "16.10.9", + "@types/progress": "2.0.5", "@types/proxy-from-env": "1.0.1", "@types/rimraf": "3.0.2", "@types/sinon": "10.0.4", diff --git a/src/initialize-node.ts b/src/initialize-node.ts index e5336654034d4..de1c7fc43a162 100644 --- a/src/initialize-node.ts +++ b/src/initialize-node.ts @@ -34,6 +34,10 @@ export const initializePuppeteerNode = (packageName: string): PuppeteerNode => { if (!isPuppeteerCore && productName === 'firefox') preferredRevision = PUPPETEER_REVISIONS.firefox; + if (!puppeteerRootDirectory) { + throw new Error('puppeteerRootDirectory is not found.'); + } + return new PuppeteerNode({ projectRoot: puppeteerRootDirectory, preferredRevision, diff --git a/src/node/Launcher.ts b/src/node/Launcher.ts index 2bb83394fe115..2f76dbf6bb1de 100644 --- a/src/node/Launcher.ts +++ b/src/node/Launcher.ts @@ -42,9 +42,9 @@ const tmpDir = () => process.env.PUPPETEER_TMP_DIR || os.tmpdir(); * @public */ export interface ProductLauncher { - launch(object: PuppeteerNodeLaunchOptions); - executablePath: (string?) => string; - defaultArgs(object: BrowserLaunchArgumentOptions); + launch(object: PuppeteerNodeLaunchOptions): Promise; + executablePath: (path?: any) => string; + defaultArgs(object: BrowserLaunchArgumentOptions): string[]; product: Product; } @@ -157,6 +157,10 @@ class ChromeLauncher implements ProductLauncher { } } + if (!chromeExecutable) { + throw new Error('chromeExecutable is not found.'); + } + const usePipe = chromeArguments.includes('--remote-debugging-pipe'); const runner = new BrowserRunner( this.product, @@ -187,7 +191,7 @@ class ChromeLauncher implements ProductLauncher { [], ignoreHTTPSErrors, defaultViewport, - runner.proc, + runner.proc ?? undefined, runner.close.bind(runner) ); } catch (error) { @@ -364,6 +368,10 @@ class FirefoxLauncher implements ProductLauncher { firefoxExecutable = executablePath; } + if (!firefoxExecutable) { + throw new Error('firefoxExecutable is not found.'); + } + const runner = new BrowserRunner( this.product, firefoxExecutable, @@ -786,7 +794,7 @@ function resolveExecutablePath(launcher: ChromeLauncher | FirefoxLauncher): { executablePath: string; missingText?: string; } { - let downloadPath: string; + let downloadPath: string | undefined; // puppeteer-core doesn't take into account PUPPETEER_* env variables. if (!launcher._isPuppeteerCore) { const executablePath = @@ -797,7 +805,7 @@ function resolveExecutablePath(launcher: ChromeLauncher | FirefoxLauncher): { const missingText = !fs.existsSync(executablePath) ? 'Tried to use PUPPETEER_EXECUTABLE_PATH env variable to launch browser but did not find any executable at: ' + executablePath - : null; + : undefined; return { executablePath, missingText }; } downloadPath = @@ -817,7 +825,7 @@ function resolveExecutablePath(launcher: ChromeLauncher | FirefoxLauncher): { const missingText = !revisionInfo.local ? 'Tried to use PUPPETEER_CHROMIUM_REVISION env variable to launch browser but did not find executable at: ' + revisionInfo.executablePath - : null; + : undefined; return { executablePath: revisionInfo.executablePath, missingText }; } } @@ -829,7 +837,7 @@ function resolveExecutablePath(launcher: ChromeLauncher | FirefoxLauncher): { ? `Could not find expected browser (${launcher.product}) locally. ${ launcher.product === 'chrome' ? chromeHelp : firefoxHelp }` - : null; + : undefined; return { executablePath: revisionInfo.executablePath, missingText }; } diff --git a/src/node/Puppeteer.ts b/src/node/Puppeteer.ts index c71a3b1cec1d9..38799f20967b2 100644 --- a/src/node/Puppeteer.ts +++ b/src/node/Puppeteer.ts @@ -66,7 +66,7 @@ import { Product } from '../common/Product.js'; * @public */ export class PuppeteerNode extends Puppeteer { - private _lazyLauncher: ProductLauncher; + private _lazyLauncher?: ProductLauncher; private _projectRoot: string; private __productName?: Product; /** @@ -108,12 +108,12 @@ export class PuppeteerNode extends Puppeteer { /** * @internal */ - get _productName(): Product { + get _productName(): Product | undefined { return this.__productName; } // don't need any TSDoc here - because the getter is internal the setter is too. - set _productName(name: Product) { + set _productName(name: Product | undefined) { if (this.__productName !== name) this._changedProduct = true; this.__productName = name; } diff --git a/src/node/install.ts b/src/node/install.ts index 856b13ae9ce3c..2c26ecaeec269 100644 --- a/src/node/install.ts +++ b/src/node/install.ts @@ -31,16 +31,24 @@ const supportedProducts = { firefox: 'Firefox Nightly', } as const; +function getProduct(input: string): 'chrome' | 'firefox' { + if (input !== 'chrome' && input !== 'firefox') { + throw new Error(`Unsupported product ${input}`); + } + return input; +} + export async function downloadBrowser(): Promise { const downloadHost = process.env.PUPPETEER_DOWNLOAD_HOST || process.env.npm_config_puppeteer_download_host || process.env.npm_package_config_puppeteer_download_host; - const product = + const product = getProduct( process.env.PUPPETEER_PRODUCT || - process.env.npm_config_puppeteer_product || - process.env.npm_package_config_puppeteer_product || - 'chrome'; + 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 || @@ -53,7 +61,7 @@ export async function downloadBrowser(): Promise { const revision = await getRevision(); await fetchBinary(revision); - function getRevision() { + async function getRevision(): Promise { if (product === 'chrome') { return ( process.env.PUPPETEER_CHROMIUM_REVISION || @@ -72,7 +80,7 @@ export async function downloadBrowser(): Promise { } } - function fetchBinary(revision) { + function fetchBinary(revision: string) { const revisionInfo = browserFetcher.revisionInfo(revision); // Do nothing if the revision is already downloaded. @@ -119,9 +127,9 @@ export async function downloadBrowser(): Promise { process.exit(1); } - let progressBar = null; + let progressBar: ProgressBar | null = null; let lastDownloadedBytes = 0; - function onProgress(downloadedBytes, totalBytes) { + function onProgress(downloadedBytes: number, totalBytes: number) { if (!progressBar) { progressBar = new ProgressBar( `Downloading ${ @@ -147,12 +155,12 @@ export async function downloadBrowser(): Promise { .catch(onError); } - function toMegabytes(bytes) { + function toMegabytes(bytes: number) { const mb = bytes / 1024 / 1024; return `${Math.round(mb * 10) / 10} Mb`; } - function getFirefoxNightlyVersion() { + async function getFirefoxNightlyVersion(): Promise { const firefoxVersionsUrl = 'https://product-details.mozilla.org/1.0/firefox_versions.json'; @@ -172,14 +180,14 @@ export async function downloadBrowser(): Promise { requestOptions.rejectUnauthorized = false; } - const promise = new Promise((resolve, reject) => { + const promise = new Promise((resolve, reject) => { let data = ''; logPolitely( `Requesting latest Firefox Nightly version from ${firefoxVersionsUrl}` ); https .get(firefoxVersionsUrl, requestOptions, (r) => { - if (r.statusCode >= 400) + if (r.statusCode && r.statusCode >= 400) return reject(new Error(`Got status code ${r.statusCode}`)); r.on('data', (chunk) => { data += chunk; @@ -200,7 +208,7 @@ export async function downloadBrowser(): Promise { } export function logPolitely(toBeLogged: unknown): void { - const logLevel = process.env.npm_config_loglevel; + const logLevel = process.env.npm_config_loglevel || ''; const logLevelDisplay = ['silent', 'error', 'warn'].indexOf(logLevel) > -1; // eslint-disable-next-line no-console