PuppeteerNode.ts (11080B)
1 /** 2 * @license 3 * Copyright 2020 Google Inc. 4 * SPDX-License-Identifier: Apache-2.0 5 */ 6 7 import { 8 Browser as browsers_SupportedBrowser, 9 resolveBuildId, 10 detectBrowserPlatform, 11 getInstalledBrowsers, 12 uninstall, 13 } from '@puppeteer/browsers'; 14 15 import type {Browser} from '../api/Browser.js'; 16 import type {Configuration} from '../common/Configuration.js'; 17 import type {ConnectOptions} from '../common/ConnectOptions.js'; 18 import {type CommonPuppeteerSettings, Puppeteer} from '../common/Puppeteer.js'; 19 import type {SupportedBrowser} from '../common/SupportedBrowser.js'; 20 import {PUPPETEER_REVISIONS} from '../revisions.js'; 21 22 import type {BrowserLauncher} from './BrowserLauncher.js'; 23 import {ChromeLauncher} from './ChromeLauncher.js'; 24 import {FirefoxLauncher} from './FirefoxLauncher.js'; 25 import type {ChromeReleaseChannel, LaunchOptions} from './LaunchOptions.js'; 26 27 /** 28 * Extends the main {@link Puppeteer} class with Node specific behaviour for 29 * fetching and downloading browsers. 30 * 31 * If you're using Puppeteer in a Node environment, this is the class you'll get 32 * when you run `require('puppeteer')` (or the equivalent ES `import`). 33 * 34 * @remarks 35 * The most common method to use is {@link PuppeteerNode.launch | launch}, which 36 * is used to launch and connect to a new browser instance. 37 * 38 * See {@link Puppeteer | the main Puppeteer class} for methods common to all 39 * environments, such as {@link Puppeteer.connect}. 40 * 41 * @example 42 * The following is a typical example of using Puppeteer to drive automation: 43 * 44 * ```ts 45 * import puppeteer from 'puppeteer'; 46 * 47 * (async () => { 48 * const browser = await puppeteer.launch(); 49 * const page = await browser.newPage(); 50 * await page.goto('https://www.google.com'); 51 * // other actions... 52 * await browser.close(); 53 * })(); 54 * ``` 55 * 56 * Once you have created a `page` you have access to a large API to interact 57 * with the page, navigate, or find certain elements in that page. 58 * The {@link Page | `page` documentation} lists all the available methods. 59 * 60 * @public 61 */ 62 export class PuppeteerNode extends Puppeteer { 63 #launcher?: BrowserLauncher; 64 #lastLaunchedBrowser?: SupportedBrowser; 65 66 /** 67 * @internal 68 */ 69 defaultBrowserRevision: string; 70 71 /** 72 * @internal 73 */ 74 configuration: Configuration = {}; 75 76 /** 77 * @internal 78 */ 79 constructor( 80 settings: { 81 configuration?: Configuration; 82 } & CommonPuppeteerSettings, 83 ) { 84 const {configuration, ...commonSettings} = settings; 85 super(commonSettings); 86 if (configuration) { 87 this.configuration = configuration; 88 } 89 switch (this.configuration.defaultBrowser) { 90 case 'firefox': 91 this.defaultBrowserRevision = PUPPETEER_REVISIONS.firefox; 92 break; 93 default: 94 this.configuration.defaultBrowser = 'chrome'; 95 this.defaultBrowserRevision = PUPPETEER_REVISIONS.chrome; 96 break; 97 } 98 99 this.connect = this.connect.bind(this); 100 this.launch = this.launch.bind(this); 101 this.executablePath = this.executablePath.bind(this); 102 this.defaultArgs = this.defaultArgs.bind(this); 103 this.trimCache = this.trimCache.bind(this); 104 } 105 106 /** 107 * This method attaches Puppeteer to an existing browser instance. 108 * 109 * @param options - Set of configurable options to set on the browser. 110 * @returns Promise which resolves to browser instance. 111 */ 112 override connect(options: ConnectOptions): Promise<Browser> { 113 return super.connect(options); 114 } 115 116 /** 117 * Launches a browser instance with given arguments and options when 118 * specified. 119 * 120 * When using with `puppeteer-core`, 121 * {@link LaunchOptions.executablePath | options.executablePath} or 122 * {@link LaunchOptions.channel | options.channel} must be provided. 123 * 124 * @example 125 * You can use {@link LaunchOptions.ignoreDefaultArgs | options.ignoreDefaultArgs} 126 * to filter out `--mute-audio` from default arguments: 127 * 128 * ```ts 129 * const browser = await puppeteer.launch({ 130 * ignoreDefaultArgs: ['--mute-audio'], 131 * }); 132 * ``` 133 * 134 * @remarks 135 * Puppeteer can also be used to control the Chrome browser, but it works best 136 * with the version of Chrome for Testing downloaded by default. 137 * There is no guarantee it will work with any other version. If Google Chrome 138 * (rather than Chrome for Testing) is preferred, a 139 * {@link https://www.google.com/chrome/browser/canary.html | Chrome Canary} 140 * or 141 * {@link https://www.chromium.org/getting-involved/dev-channel | Dev Channel} 142 * build is suggested. See 143 * {@link https://www.howtogeek.com/202825/what%E2%80%99s-the-difference-between-chromium-and-chrome/ | this article} 144 * for a description of the differences between Chromium and Chrome. 145 * {@link https://chromium.googlesource.com/chromium/src/+/lkgr/docs/chromium_browser_vs_google_chrome.md | This article} 146 * describes some differences for Linux users. See 147 * {@link https://developer.chrome.com/blog/chrome-for-testing/ | this doc} for the description 148 * of Chrome for Testing. 149 * 150 * @param options - Options to configure launching behavior. 151 */ 152 launch(options: LaunchOptions = {}): Promise<Browser> { 153 const {browser = this.defaultBrowser} = options; 154 this.#lastLaunchedBrowser = browser; 155 switch (browser) { 156 case 'chrome': 157 this.defaultBrowserRevision = PUPPETEER_REVISIONS.chrome; 158 break; 159 case 'firefox': 160 this.defaultBrowserRevision = PUPPETEER_REVISIONS.firefox; 161 break; 162 default: 163 throw new Error(`Unknown product: ${browser}`); 164 } 165 this.#launcher = this.#getLauncher(browser); 166 return this.#launcher.launch(options); 167 } 168 169 /** 170 * @internal 171 */ 172 #getLauncher(browser: SupportedBrowser): BrowserLauncher { 173 if (this.#launcher && this.#launcher.browser === browser) { 174 return this.#launcher; 175 } 176 switch (browser) { 177 case 'chrome': 178 return new ChromeLauncher(this); 179 case 'firefox': 180 return new FirefoxLauncher(this); 181 default: 182 throw new Error(`Unknown product: ${browser}`); 183 } 184 } 185 186 /** 187 * The default executable path for a given ChromeReleaseChannel. 188 */ 189 executablePath(channel: ChromeReleaseChannel): string; 190 /** 191 * The default executable path given LaunchOptions. 192 */ 193 executablePath(options: LaunchOptions): string; 194 /** 195 * The default executable path. 196 */ 197 executablePath(): string; 198 executablePath(optsOrChannel?: ChromeReleaseChannel | LaunchOptions): string { 199 if (optsOrChannel === undefined) { 200 return this.#getLauncher(this.lastLaunchedBrowser).executablePath( 201 undefined, 202 /* validatePath= */ false, 203 ); 204 } 205 if (typeof optsOrChannel === 'string') { 206 return this.#getLauncher('chrome').executablePath( 207 optsOrChannel, 208 /* validatePath= */ false, 209 ); 210 } 211 return this.#getLauncher( 212 optsOrChannel.browser ?? this.lastLaunchedBrowser, 213 ).resolveExecutablePath(optsOrChannel.headless, /* validatePath= */ false); 214 } 215 216 /** 217 * @internal 218 */ 219 get browserVersion(): string { 220 return ( 221 this.configuration?.[this.lastLaunchedBrowser]?.version ?? 222 this.defaultBrowserRevision! 223 ); 224 } 225 226 /** 227 * The default download path for puppeteer. For puppeteer-core, this 228 * code should never be called as it is never defined. 229 * 230 * @internal 231 */ 232 get defaultDownloadPath(): string | undefined { 233 return this.configuration.cacheDirectory; 234 } 235 236 /** 237 * The name of the browser that was last launched. 238 */ 239 get lastLaunchedBrowser(): SupportedBrowser { 240 return this.#lastLaunchedBrowser ?? this.defaultBrowser; 241 } 242 243 /** 244 * The name of the browser that will be launched by default. For 245 * `puppeteer`, this is influenced by your configuration. Otherwise, it's 246 * `chrome`. 247 */ 248 get defaultBrowser(): SupportedBrowser { 249 return this.configuration.defaultBrowser ?? 'chrome'; 250 } 251 252 /** 253 * @deprecated Do not use as this field as it does not take into account 254 * multiple browsers of different types. Use 255 * {@link PuppeteerNode.defaultBrowser | defaultBrowser} or 256 * {@link PuppeteerNode.lastLaunchedBrowser | lastLaunchedBrowser}. 257 * 258 * @returns The name of the browser that is under automation. 259 */ 260 get product(): string { 261 return this.lastLaunchedBrowser; 262 } 263 264 /** 265 * @param options - Set of configurable options to set on the browser. 266 * 267 * @returns The default arguments that the browser will be launched with. 268 */ 269 defaultArgs(options: LaunchOptions = {}): string[] { 270 return this.#getLauncher( 271 options.browser ?? this.lastLaunchedBrowser, 272 ).defaultArgs(options); 273 } 274 275 /** 276 * Removes all non-current Firefox and Chrome binaries in the cache directory 277 * identified by the provided Puppeteer configuration. The current browser 278 * version is determined by resolving PUPPETEER_REVISIONS from Puppeteer 279 * unless `configuration.browserRevision` is provided. 280 * 281 * @remarks 282 * 283 * Note that the method does not check if any other Puppeteer versions 284 * installed on the host that use the same cache directory require the 285 * non-current binaries. 286 * 287 * @public 288 */ 289 async trimCache(): Promise<void> { 290 const platform = detectBrowserPlatform(); 291 if (!platform) { 292 throw new Error('The current platform is not supported.'); 293 } 294 295 const cacheDir = this.configuration.cacheDirectory!; 296 const installedBrowsers = await getInstalledBrowsers({ 297 cacheDir, 298 }); 299 300 const puppeteerBrowsers: Array<{ 301 product: SupportedBrowser; 302 browser: browsers_SupportedBrowser; 303 currentBuildId: string; 304 }> = [ 305 { 306 product: 'chrome', 307 browser: browsers_SupportedBrowser.CHROME, 308 currentBuildId: '', 309 }, 310 { 311 product: 'firefox', 312 browser: browsers_SupportedBrowser.FIREFOX, 313 currentBuildId: '', 314 }, 315 ]; 316 317 // Resolve current buildIds. 318 for (const item of puppeteerBrowsers) { 319 const tag = 320 this.configuration?.[item.product]?.version ?? 321 PUPPETEER_REVISIONS[item.product]; 322 323 item.currentBuildId = await resolveBuildId(item.browser, platform, tag); 324 } 325 326 const currentBrowserBuilds = new Set( 327 puppeteerBrowsers.map(browser => { 328 return `${browser.browser}_${browser.currentBuildId}`; 329 }), 330 ); 331 332 const currentBrowsers = new Set( 333 puppeteerBrowsers.map(browser => { 334 return browser.browser; 335 }), 336 ); 337 338 for (const installedBrowser of installedBrowsers) { 339 // Don't uninstall browsers that are not managed by Puppeteer yet. 340 if (!currentBrowsers.has(installedBrowser.browser)) { 341 continue; 342 } 343 // Keep the browser build used by the current Puppeteer installation. 344 if ( 345 currentBrowserBuilds.has( 346 `${installedBrowser.browser}_${installedBrowser.buildId}`, 347 ) 348 ) { 349 continue; 350 } 351 352 await uninstall({ 353 browser: installedBrowser.browser, 354 platform, 355 cacheDir, 356 buildId: installedBrowser.buildId, 357 }); 358 } 359 } 360 }