/** * @license * Copyright 2020 Google Inc. * SPDX-License-Identifier: Apache-2.0 */ import { Browser as browsers_SupportedBrowser, resolveBuildId, detectBrowserPlatform, getInstalledBrowsers, uninstall, } from '@puppeteer/browsers'; import type {Browser} from '../api/Browser.js'; import type {Configuration} from '../common/Configuration.js'; import type { ConnectOptions, BrowserConnectOptions, } from '../common/ConnectOptions.js'; import {type CommonPuppeteerSettings, Puppeteer} from '../common/Puppeteer.js'; import type {SupportedBrowser} from '../common/SupportedBrowser.js'; import {PUPPETEER_REVISIONS} from '../revisions.js'; import type {BrowserLauncher} from './BrowserLauncher.js'; import {ChromeLauncher} from './ChromeLauncher.js'; import {FirefoxLauncher} from './FirefoxLauncher.js'; import type { BrowserLaunchArgumentOptions, ChromeReleaseChannel, LaunchOptions, } from './LaunchOptions.js'; /** * @public */ export interface PuppeteerLaunchOptions extends LaunchOptions, BrowserLaunchArgumentOptions, BrowserConnectOptions { browser?: SupportedBrowser; extraPrefsFirefox?: Record<string, unknown>; } /** * Extends the main {@link Puppeteer} class with Node specific behaviour for * fetching and downloading browsers. * * If you're using Puppeteer in a Node environment, this is the class you'll get * when you run `require('puppeteer')` (or the equivalent ES `import`). * * @remarks * The most common method to use is {@link PuppeteerNode.launch | launch}, which * is used to launch and connect to a new browser instance. * * See {@link Puppeteer | the main Puppeteer class} for methods common to all * environments, such as {@link Puppeteer.connect}. * * @example * The following is a typical example of using Puppeteer to drive automation: * * ```ts * import puppeteer from 'puppeteer'; * * (async () => { * const browser = await puppeteer.launch(); * const page = await browser.newPage(); * await page.goto('https://www.google.com'); * // other actions... * await browser.close(); * })(); * ``` * * Once you have created a `page` you have access to a large API to interact * with the page, navigate, or find certain elements in that page. * The {@link Page | `page` documentation} lists all the available methods. * * @public */ export class PuppeteerNode extends Puppeteer { #_launcher?: BrowserLauncher; #lastLaunchedBrowser?: SupportedBrowser; /** * @internal */ defaultBrowserRevision: string; /** * @internal */ configuration: Configuration = {}; /** * @internal */ constructor( settings: { configuration?: Configuration; } & CommonPuppeteerSettings ) { const {configuration, ...commonSettings} = settings; super(commonSettings); if (configuration) { this.configuration = configuration; } switch (this.configuration.defaultBrowser) { case 'firefox': this.defaultBrowserRevision = PUPPETEER_REVISIONS.firefox; break; default: this.configuration.defaultBrowser = 'chrome'; this.defaultBrowserRevision = PUPPETEER_REVISIONS.chrome; break; } this.connect = this.connect.bind(this); this.launch = this.launch.bind(this); this.executablePath = this.executablePath.bind(this); this.defaultArgs = this.defaultArgs.bind(this); this.trimCache = this.trimCache.bind(this); } /** * This method attaches Puppeteer to an existing browser instance. * * @param options - Set of configurable options to set on the browser. * @returns Promise which resolves to browser instance. */ override connect(options: ConnectOptions): Promise<Browser> { return super.connect(options); } /** * Launches a browser instance with given arguments and options when * specified. * * When using with `puppeteer-core`, * {@link LaunchOptions.executablePath | options.executablePath} or * {@link LaunchOptions.channel | options.channel} must be provided. * * @example * You can use {@link LaunchOptions.ignoreDefaultArgs | options.ignoreDefaultArgs} * to filter out `--mute-audio` from default arguments: * * ```ts * const browser = await puppeteer.launch({ * ignoreDefaultArgs: ['--mute-audio'], * }); * ``` * * @remarks * Puppeteer can also be used to control the Chrome browser, but it works best * with the version of Chrome for Testing downloaded by default. * There is no guarantee it will work with any other version. If Google Chrome * (rather than Chrome for Testing) 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. 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. See * {@link https://developer.chrome.com/blog/chrome-for-testing/ | this doc} for the description * of Chrome for Testing. * * @param options - Options to configure launching behavior. */ launch(options: PuppeteerLaunchOptions = {}): Promise<Browser> { const {browser: product = this.defaultBrowser} = options; this.#lastLaunchedBrowser = product; return this.#launcher.launch(options); } /** * @internal */ get #launcher(): BrowserLauncher { if ( this.#_launcher && this.#_launcher.browser === this.lastLaunchedBrowser ) { return this.#_launcher; } switch (this.lastLaunchedBrowser) { case 'chrome': this.defaultBrowserRevision = PUPPETEER_REVISIONS.chrome; 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.#lastLaunchedBrowser}`); } return this.#_launcher; } /** * The default executable path. */ executablePath(channel?: ChromeReleaseChannel): string { return this.#launcher.executablePath(channel); } /** * @internal */ get browserVersion(): string { return ( this.configuration?.[this.lastLaunchedBrowser]?.version ?? this.defaultBrowserRevision! ); } /** * The default download path for puppeteer. For puppeteer-core, this * code should never be called as it is never defined. * * @internal */ get defaultDownloadPath(): string | undefined { return this.configuration.cacheDirectory; } /** * The name of the browser that was last launched. */ get lastLaunchedBrowser(): SupportedBrowser { return this.#lastLaunchedBrowser ?? this.defaultBrowser; } /** * The name of the browser that will be launched by default. For * `puppeteer`, this is influenced by your configuration. Otherwise, it's * `chrome`. */ get defaultBrowser(): SupportedBrowser { return this.configuration.defaultBrowser ?? 'chrome'; } /** * @deprecated Do not use as this field as it does not take into account * multiple browsers of different types. Use * {@link PuppeteerNode.defaultBrowser | defaultBrowser} or * {@link PuppeteerNode.lastLaunchedBrowser | lastLaunchedBrowser}. * * @returns The name of the browser that is under automation. */ get product(): string { return this.#launcher.browser; } /** * @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); } /** * Removes all non-current Firefox and Chrome binaries in the cache directory * identified by the provided Puppeteer configuration. The current browser * version is determined by resolving PUPPETEER_REVISIONS from Puppeteer * unless `configuration.browserRevision` is provided. * * @remarks * * Note that the method does not check if any other Puppeteer versions * installed on the host that use the same cache directory require the * non-current binaries. * * @public */ async trimCache(): Promise<void> { const platform = detectBrowserPlatform(); if (!platform) { throw new Error('The current platform is not supported.'); } const cacheDir = this.configuration.cacheDirectory!; const installedBrowsers = await getInstalledBrowsers({ cacheDir, }); const puppeteerBrowsers: Array<{ product: SupportedBrowser; browser: browsers_SupportedBrowser; currentBuildId: string; }> = [ { product: 'chrome', browser: browsers_SupportedBrowser.CHROME, currentBuildId: '', }, { product: 'firefox', browser: browsers_SupportedBrowser.FIREFOX, currentBuildId: '', }, ]; // Resolve current buildIds. for (const item of puppeteerBrowsers) { const tag = this.configuration?.[item.product]?.version ?? PUPPETEER_REVISIONS[item.product]; item.currentBuildId = await resolveBuildId(item.browser, platform, tag); } const currentBrowserBuilds = new Set( puppeteerBrowsers.map(browser => { return `${browser.browser}_${browser.currentBuildId}`; }) ); const currentBrowsers = new Set( puppeteerBrowsers.map(browser => { return browser.browser; }) ); for (const installedBrowser of installedBrowsers) { // Don't uninstall browsers that are not managed by Puppeteer yet. if (!currentBrowsers.has(installedBrowser.browser)) { continue; } // Keep the browser build used by the current Puppeteer installation. if ( currentBrowserBuilds.has( `${installedBrowser.browser}_${installedBrowser.buildId}` ) ) { continue; } await uninstall({ browser: installedBrowser.browser, platform, cacheDir, buildId: installedBrowser.buildId, }); } } }