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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
55 changed files with 925 additions and 706 deletions

View File

@ -30,6 +30,7 @@ sidebar_label: API
| [Keyboard](./puppeteer.keyboard.md) | Keyboard provides an api for managing a virtual keyboard. The high level api is [Keyboard.type()](./puppeteer.keyboard.type.md), which takes raw characters and generates proper keydown, keypress/input, and keyup events on your page. |
| [Mouse](./puppeteer.mouse.md) | The Mouse class operates in main-frame CSS pixels relative to the top-left corner of the viewport. |
| [Page](./puppeteer.page.md) | <p>Page provides methods to interact with a single tab or [extension background page](https://developer.chrome.com/extensions/background_pages) in Chromium.</p><p>:::note</p><p>One Browser instance might have multiple Page instances.</p><p>:::</p> |
| [ProductLauncher](./puppeteer.productlauncher.md) | Describes a launcher - a class that is able to create and launch a browser instance. |
| [ProtocolError](./puppeteer.protocolerror.md) | ProtocolError is emitted whenever there is an error from the protocol. |
| [Puppeteer](./puppeteer.puppeteer.md) | <p>The main Puppeteer class.</p><p>IMPORTANT: if you are using Puppeteer in a Node environment, you will get an instance of [PuppeteerNode](./puppeteer.puppeteernode.md) when you import or require <code>puppeteer</code>. That class extends <code>Puppeteer</code>, so has all the methods documented below as well as all that are defined on [PuppeteerNode](./puppeteer.puppeteernode.md).</p> |
| [PuppeteerNode](./puppeteer.puppeteernode.md) | <p>Extends the main [Puppeteer](./puppeteer.puppeteer.md) class with Node specific behaviour for fetching and downloading browsers.</p><p>If you're using Puppeteer in a Node environment, this is the class you'll get when you run <code>require('puppeteer')</code> (or the equivalent ES <code>import</code>).</p> |
@ -72,6 +73,7 @@ sidebar_label: API
| [CDPSessionOnMessageObject](./puppeteer.cdpsessiononmessageobject.md) | |
| [ClickOptions](./puppeteer.clickoptions.md) | |
| [CommonEventEmitter](./puppeteer.commoneventemitter.md) | |
| [Configuration](./puppeteer.configuration.md) | |
| [ConnectionCallback](./puppeteer.connectioncallback.md) | |
| [ConnectionTransport](./puppeteer.connectiontransport.md) | |
| [ConnectOptions](./puppeteer.connectoptions.md) | |
@ -102,7 +104,6 @@ sidebar_label: API
| [PDFOptions](./puppeteer.pdfoptions.md) | Valid options to configure PDF generation via [Page.pdf()](./puppeteer.page.pdf.md). |
| [Point](./puppeteer.point.md) | |
| [PressOptions](./puppeteer.pressoptions.md) | |
| [ProductLauncher](./puppeteer.productlauncher.md) | Describes a launcher - a class that is able to create and launch a browser instance. |
| [PuppeteerErrors](./puppeteer.puppeteererrors.md) | |
| [PuppeteerLaunchOptions](./puppeteer.puppeteerlaunchoptions.md) | |
| [RemoteAddress](./puppeteer.remoteaddress.md) | |

View File

@ -10,12 +10,12 @@ Constructs a browser fetcher for the given options.
```typescript
class BrowserFetcher {
constructor(options?: BrowserFetcherOptions);
constructor(options: BrowserFetcherOptions);
}
```
## Parameters
| Parameter | Type | Description |
| --------- | ------------------------------------------------------------- | ----------------- |
| options | [BrowserFetcherOptions](./puppeteer.browserfetcheroptions.md) | <i>(Optional)</i> |
| --------- | ------------------------------------------------------------- | ----------- |
| options | [BrowserFetcherOptions](./puppeteer.browserfetcheroptions.md) | |

View File

@ -8,15 +8,15 @@ sidebar_label: BrowserFetcher.localRevisions
```typescript
class BrowserFetcher {
localRevisions(): Promise<string[]>;
localRevisions(): string[];
}
```
**Returns:**
Promise&lt;string\[\]&gt;
string\[\]
A promise with a list of all revision strings (for the current `product`) available locally on disk.
A list of all revision strings (for the current `product`) available locally on disk.
## Remarks

View File

@ -21,7 +21,7 @@ BrowserFetcher is not designed to work concurrently with other instances of Brow
An example of using BrowserFetcher to download a specific version of Chromium 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,

View File

@ -13,8 +13,9 @@ export interface BrowserFetcherOptions
## Properties
| Property | Modifiers | Type | Description |
| ---------------------------------------------------------- | --------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------ |
| ---------------------------------------------------------------------------- | --------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------ |
| [host?](./puppeteer.browserfetcheroptions.host.md) | | string | <i>(Optional)</i> Determines the host that will be used for downloading. |
| [path?](./puppeteer.browserfetcheroptions.path.md) | | string | <i>(Optional)</i> Determines the path to download browsers to. |
| [path](./puppeteer.browserfetcheroptions.path.md) | | string | Determines the path to download browsers to. |
| [platform?](./puppeteer.browserfetcheroptions.platform.md) | | [Platform](./puppeteer.platform.md) | <i>(Optional)</i> Determines which platform the browser will be suited for. |
| [product?](./puppeteer.browserfetcheroptions.product.md) | | 'chrome' \| 'firefox' | <i>(Optional)</i> Determines which product the [BrowserFetcher](./puppeteer.browserfetcher.md) is for. |
| [useMacOSARMBinary?](./puppeteer.browserfetcheroptions.usemacosarmbinary.md) | | boolean | <i>(Optional)</i> Enables the use of the Chromium binary for macOS ARM. |

View File

@ -10,6 +10,6 @@ Determines the path to download browsers to.
```typescript
interface BrowserFetcherOptions {
path?: string;
path: string;
}
```

View File

@ -0,0 +1,15 @@
---
sidebar_label: BrowserFetcherOptions.useMacOSARMBinary
---
# BrowserFetcherOptions.useMacOSARMBinary property
Enables the use of the Chromium binary for macOS ARM.
**Signature:**
```typescript
interface BrowserFetcherOptions {
useMacOSARMBinary?: boolean;
}
```

View File

@ -0,0 +1,13 @@
---
sidebar_label: Configuration.browserRevision
---
# Configuration.browserRevision property
**Signature:**
```typescript
interface Configuration {
browserRevision?: string;
}
```

View File

@ -0,0 +1,13 @@
---
sidebar_label: Configuration.cacheDirectory
---
# Configuration.cacheDirectory property
**Signature:**
```typescript
interface Configuration {
cacheDirectory?: string;
}
```

View File

@ -0,0 +1,13 @@
---
sidebar_label: Configuration.defaultProduct
---
# Configuration.defaultProduct property
**Signature:**
```typescript
interface Configuration {
defaultProduct?: Product;
}
```

View File

@ -0,0 +1,13 @@
---
sidebar_label: Configuration.downloadHost
---
# Configuration.downloadHost property
**Signature:**
```typescript
interface Configuration {
downloadHost?: string;
}
```

View File

@ -0,0 +1,13 @@
---
sidebar_label: Configuration.downloadPath
---
# Configuration.downloadPath property
**Signature:**
```typescript
interface Configuration {
downloadPath?: string;
}
```

View File

@ -0,0 +1,13 @@
---
sidebar_label: Configuration.executablePath
---
# Configuration.executablePath property
**Signature:**
```typescript
interface Configuration {
executablePath?: string;
}
```

View File

@ -0,0 +1,15 @@
---
sidebar_label: Configuration.experiments
---
# Configuration.experiments property
**Signature:**
```typescript
interface Configuration {
experiments?: {
macArmChromiumEnabled?: boolean;
};
}
```

View File

@ -0,0 +1,13 @@
---
sidebar_label: Configuration.logLevel
---
# Configuration.logLevel property
**Signature:**
```typescript
interface Configuration {
logLevel?: 'silent' | 'error' | 'warn';
}
```

View File

@ -0,0 +1,26 @@
---
sidebar_label: Configuration
---
# Configuration interface
**Signature:**
```typescript
export interface Configuration
```
## Properties
| Property | Modifiers | Type | Description |
| ---------------------------------------------------------------------- | --------- | ------------------------------------ | ----------------- |
| [browserRevision?](./puppeteer.configuration.browserrevision.md) | | string | <i>(Optional)</i> |
| [cacheDirectory?](./puppeteer.configuration.cachedirectory.md) | | string | <i>(Optional)</i> |
| [defaultProduct?](./puppeteer.configuration.defaultproduct.md) | | [Product](./puppeteer.product.md) | <i>(Optional)</i> |
| [downloadHost?](./puppeteer.configuration.downloadhost.md) | | string | <i>(Optional)</i> |
| [downloadPath?](./puppeteer.configuration.downloadpath.md) | | string | <i>(Optional)</i> |
| [executablePath?](./puppeteer.configuration.executablepath.md) | | string | <i>(Optional)</i> |
| [experiments?](./puppeteer.configuration.experiments.md) | | { macArmChromiumEnabled?: boolean; } | <i>(Optional)</i> |
| [logLevel?](./puppeteer.configuration.loglevel.md) | | 'silent' \| 'error' \| 'warn' | <i>(Optional)</i> |
| [skipDownload?](./puppeteer.configuration.skipdownload.md) | | boolean | <i>(Optional)</i> |
| [temporaryDirectory?](./puppeteer.configuration.temporarydirectory.md) | | string | <i>(Optional)</i> |

View File

@ -0,0 +1,13 @@
---
sidebar_label: Configuration.skipDownload
---
# Configuration.skipDownload property
**Signature:**
```typescript
interface Configuration {
skipDownload?: boolean;
}
```

View File

@ -0,0 +1,13 @@
---
sidebar_label: Configuration.temporaryDirectory
---
# Configuration.temporaryDirectory property
**Signature:**
```typescript
interface Configuration {
temporaryDirectory?: string;
}
```

View File

@ -9,5 +9,5 @@ sidebar_label: connect
```typescript
connect: (
options: import('puppeteer-core/internal/common/Puppeteer.js').ConnectOptions
) => Promise<import('./types').Browser>;
) => Promise<import('./types.js').Browser>;
```

View File

@ -8,6 +8,8 @@ sidebar_label: createBrowserFetcher
```typescript
createBrowserFetcher: (
options: import('puppeteer-core/internal/node/BrowserFetcher.js').BrowserFetcherOptions
options: Partial<
import('puppeteer-core/internal/node/BrowserFetcher.js').BrowserFetcherOptions
>
) => import('puppeteer-core/internal/node/BrowserFetcher.js').BrowserFetcher;
```

View File

@ -7,5 +7,9 @@ sidebar_label: executablePath
**Signature:**
```typescript
executablePath: (channel?: string | undefined) => string;
executablePath: (
channel?:
| import('puppeteer-core/internal/node/LaunchOptions.js').ChromeReleaseChannel
| undefined
) => string;
```

View File

@ -11,5 +11,5 @@ launch: (
options?:
| import('puppeteer-core/internal/node/PuppeteerNode.js').PuppeteerLaunchOptions
| undefined
) => Promise<import('./types').Browser>;
) => Promise<import('./types.js').Browser>;
```

View File

@ -7,7 +7,7 @@ sidebar_label: ProductLauncher.defaultArgs
**Signature:**
```typescript
interface ProductLauncher {
class ProductLauncher {
defaultArgs(object: BrowserLaunchArgumentOptions): string[];
}
```

View File

@ -2,12 +2,22 @@
sidebar_label: ProductLauncher.executablePath
---
# ProductLauncher.executablePath property
# ProductLauncher.executablePath() method
**Signature:**
```typescript
interface ProductLauncher {
executablePath: (path?: any) => string;
class ProductLauncher {
executablePath(channel?: ChromeReleaseChannel): string;
}
```
## Parameters
| Parameter | Type | Description |
| --------- | ----------------------------------------------------------- | ----------------- |
| channel | [ChromeReleaseChannel](./puppeteer.chromereleasechannel.md) | <i>(Optional)</i> |
**Returns:**
string

View File

@ -7,7 +7,7 @@ sidebar_label: ProductLauncher.launch
**Signature:**
```typescript
interface ProductLauncher {
class ProductLauncher {
launch(object: PuppeteerNodeLaunchOptions): Promise<Browser>;
}
```

View File

@ -2,26 +2,30 @@
sidebar_label: ProductLauncher
---
# ProductLauncher interface
# ProductLauncher class
Describes a launcher - a class that is able to create and launch a browser instance.
**Signature:**
```typescript
export interface ProductLauncher
export declare class ProductLauncher
```
## Remarks
The constructor for this class is marked as internal. Third-party code should not call the constructor directly or create subclasses that extend the `ProductLauncher` class.
## Properties
| Property | Modifiers | Type | Description |
| --------------------------------------------------------------- | --------- | --------------------------------- | ----------- |
| [executablePath](./puppeteer.productlauncher.executablepath.md) | | (path?: any) =&gt; string | |
| [product](./puppeteer.productlauncher.product.md) | | [Product](./puppeteer.product.md) | |
| ------------------------------------------------- | --------------------- | --------------------------------- | ----------- |
| [product](./puppeteer.productlauncher.product.md) | <code>readonly</code> | [Product](./puppeteer.product.md) | |
## Methods
| Method | Description |
| ----------------------------------------------------------------- | ----------- |
| [defaultArgs(object)](./puppeteer.productlauncher.defaultargs.md) | |
| [launch(object)](./puppeteer.productlauncher.launch.md) | |
| Method | Modifiers | Description |
| ------------------------------------------------------------------------ | --------- | ----------- |
| [defaultArgs(object)](./puppeteer.productlauncher.defaultargs.md) | | |
| [executablePath(channel)](./puppeteer.productlauncher.executablepath.md) | | |
| [launch(object)](./puppeteer.productlauncher.launch.md) | | |

View File

@ -7,7 +7,7 @@ sidebar_label: ProductLauncher.product
**Signature:**
```typescript
interface ProductLauncher {
product: Product;
class ProductLauncher {
get product(): Product;
}
```

View File

@ -12,15 +12,15 @@ sidebar_label: PuppeteerNode.createBrowserFetcher
```typescript
class PuppeteerNode {
createBrowserFetcher(options: BrowserFetcherOptions): BrowserFetcher;
createBrowserFetcher(options: Partial<BrowserFetcherOptions>): BrowserFetcher;
}
```
## Parameters
| Parameter | Type | Description |
| --------- | ------------------------------------------------------------- | -------------------------------------------------------------------------- |
| options | [BrowserFetcherOptions](./puppeteer.browserfetcheroptions.md) | Set of configurable options to specify the settings of the BrowserFetcher. |
| --------- | ---------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
| options | Partial&lt;[BrowserFetcherOptions](./puppeteer.browserfetcheroptions.md)&gt; | Set of configurable options to specify the settings of the BrowserFetcher. |
**Returns:**

View File

@ -0,0 +1,13 @@
---
sidebar_label: PuppeteerNode.defaultProduct
---
# PuppeteerNode.defaultProduct property
**Signature:**
```typescript
class PuppeteerNode {
get defaultProduct(): Product;
}
```

View File

@ -8,22 +8,18 @@ sidebar_label: PuppeteerNode.executablePath
```typescript
class PuppeteerNode {
executablePath(channel?: string): string;
executablePath(channel?: ChromeReleaseChannel): string;
}
```
## Parameters
| Parameter | Type | Description |
| --------- | ------ | ----------------- |
| channel | string | <i>(Optional)</i> |
| --------- | ----------------------------------------------------------- | ----------------- |
| channel | [ChromeReleaseChannel](./puppeteer.chromereleasechannel.md) | <i>(Optional)</i> |
**Returns:**
string
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.
## Remarks
**NOTE** `puppeteer.executablePath()` is affected by the `PUPPETEER_EXECUTABLE_PATH` and `PUPPETEER_CHROMIUM_REVISION` environment variables.
The executable path.

View File

@ -0,0 +1,13 @@
---
sidebar_label: PuppeteerNode.lastLaunchedProduct
---
# PuppeteerNode.lastLaunchedProduct property
**Signature:**
```typescript
class PuppeteerNode {
get lastLaunchedProduct(): Product;
}
```

View File

@ -4,7 +4,7 @@ sidebar_label: PuppeteerNode.launch
# PuppeteerNode.launch() method
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.
**Signature:**
@ -17,18 +17,16 @@ class PuppeteerNode {
## Parameters
| Parameter | Type | Description |
| --------- | --------------------------------------------------------------- | -------------------------------------------------------------------- |
| options | [PuppeteerLaunchOptions](./puppeteer.puppeteerlaunchoptions.md) | <i>(Optional)</i> Set of configurable options to set on the browser. |
| --------- | --------------------------------------------------------------- | ---------------------------------------------------------- |
| options | [PuppeteerLaunchOptions](./puppeteer.puppeteerlaunchoptions.md) | <i>(Optional)</i> Options to configure launching behavior. |
**Returns:**
Promise&lt;[Browser](./puppeteer.browser.md)&gt;
Promise which resolves to browser instance.
## 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 [Chrome Canary](https://www.google.com/chrome/browser/canary.html) or [Dev Channel](https://www.chromium.org/getting-involved/dev-channel) build is suggested. In `puppeteer.launch([options])`, any mention of Chromium also applies to Chrome. See [this article](https://www.howtogeek.com/202825/what%E2%80%99s-the-difference-between-chromium-and-chrome/) for a description of the differences between Chromium and Chrome. [This article](https://chromium.googlesource.com/chromium/src/+/lkgr/docs/chromium_browser_vs_google_chrome.md) describes some differences for Linux users.
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 [Chrome Canary](https://www.google.com/chrome/browser/canary.html) or [Dev Channel](https://www.chromium.org/getting-involved/dev-channel) build is suggested. In , any mention of Chromium also applies to Chrome. See [this article](https://www.howtogeek.com/202825/what%E2%80%99s-the-difference-between-chromium-and-chrome/) for a description of the differences between Chromium and Chrome. [This article](https://chromium.googlesource.com/chromium/src/+/lkgr/docs/chromium_browser_vs_google_chrome.md) describes some differences for Linux users.
## Example

View File

@ -45,15 +45,17 @@ Once you have created a `page` you have access to a large API to interact with t
## Properties
| Property | Modifiers | Type | Description |
| ----------------------------------------------- | --------------------- | ------ | ---------------------------------------------------------------------------------------------------------------------- |
| [product](./puppeteer.puppeteernode.product.md) | <code>readonly</code> | string | The name of the browser that is under automation (<code>&quot;chrome&quot;</code> or <code>&quot;firefox&quot;</code>) |
| ----------------------------------------------------------------------- | --------------------- | --------------------------------- | ----------- |
| [defaultProduct](./puppeteer.puppeteernode.defaultproduct.md) | <code>readonly</code> | [Product](./puppeteer.product.md) | |
| [lastLaunchedProduct](./puppeteer.puppeteernode.lastlaunchedproduct.md) | <code>readonly</code> | [Product](./puppeteer.product.md) | |
| [product](./puppeteer.puppeteernode.product.md) | <code>readonly</code> | string | |
## Methods
| Method | Modifiers | Description |
| ---------------------------------------------------------------------------------- | --------- | --------------------------------------------------------------------------------------------------- |
| ---------------------------------------------------------------------------------- | --------- | ---------------------------------------------------------------------------- |
| [connect(options)](./puppeteer.puppeteernode.connect.md) | | This method attaches Puppeteer to an existing browser instance. |
| [createBrowserFetcher(options)](./puppeteer.puppeteernode.createbrowserfetcher.md) | | |
| [defaultArgs(options)](./puppeteer.puppeteernode.defaultargs.md) | | |
| [executablePath(channel)](./puppeteer.puppeteernode.executablepath.md) | | |
| [launch(options)](./puppeteer.puppeteernode.launch.md) | | Launches puppeteer and launches a browser instance with given arguments and options when specified. |
| [launch(options)](./puppeteer.puppeteernode.launch.md) | | Launches a browser instance with given arguments and options when specified. |

View File

@ -4,7 +4,9 @@ sidebar_label: PuppeteerNode.product
# PuppeteerNode.product property
The name of the browser that is under automation (`"chrome"` or `"firefox"`)
> Warning: This API is now obsolete.
>
> Do not use as this field as it does not take into account multiple browsers of different types. Use or .
**Signature:**
@ -13,7 +15,3 @@ class PuppeteerNode {
get product(): string;
}
```
## 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.

88
package-lock.json generated
View File

@ -84,7 +84,6 @@
"version": "7.12.11",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz",
"integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==",
"dev": true,
"dependencies": {
"@babel/highlight": "^7.10.4"
}
@ -93,7 +92,6 @@
"version": "7.19.1",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
"integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==",
"dev": true,
"engines": {
"node": ">=6.9.0"
}
@ -102,7 +100,6 @@
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz",
"integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==",
"dev": true,
"dependencies": {
"@babel/helper-validator-identifier": "^7.18.6",
"chalk": "^2.0.0",
@ -116,7 +113,6 @@
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"dependencies": {
"color-convert": "^1.9.0"
},
@ -128,7 +124,6 @@
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"dev": true,
"dependencies": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
@ -142,7 +137,6 @@
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"dev": true,
"dependencies": {
"color-name": "1.1.3"
}
@ -150,14 +144,12 @@
"node_modules/@babel/highlight/node_modules/color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
"dev": true
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
},
"node_modules/@babel/highlight/node_modules/escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
"dev": true,
"engines": {
"node": ">=0.8.0"
}
@ -166,7 +158,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
"dev": true,
"engines": {
"node": ">=4"
}
@ -175,7 +166,6 @@
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"dependencies": {
"has-flag": "^3.0.0"
},
@ -1916,8 +1906,7 @@
"node_modules/@types/parse-json": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",
"integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==",
"dev": true
"integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA=="
},
"node_modules/@types/pixelmatch": {
"version": "5.2.4",
@ -2650,7 +2639,6 @@
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
"dev": true,
"engines": {
"node": ">=6"
}
@ -2901,7 +2889,6 @@
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz",
"integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==",
"dev": true,
"dependencies": {
"@types/parse-json": "^4.0.0",
"import-fresh": "^3.2.1",
@ -3135,7 +3122,6 @@
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
"integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
"dev": true,
"dependencies": {
"is-arrayish": "^0.2.1"
}
@ -4993,7 +4979,6 @@
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
"integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
"dev": true,
"dependencies": {
"parent-module": "^1.0.0",
"resolve-from": "^4.0.0"
@ -5009,7 +4994,6 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
"dev": true,
"engines": {
"node": ">=4"
}
@ -5111,8 +5095,7 @@
"node_modules/is-arrayish": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
"integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
"dev": true
"integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="
},
"node_modules/is-bigint": {
"version": "1.0.4",
@ -5558,8 +5541,7 @@
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"dev": true
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
"node_modules/js-yaml": {
"version": "3.13.1",
@ -5583,8 +5565,7 @@
"node_modules/json-parse-even-better-errors": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
"dev": true
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="
},
"node_modules/json-schema-traverse": {
"version": "1.0.0",
@ -5684,8 +5665,7 @@
"node_modules/lines-and-columns": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
"dev": true
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="
},
"node_modules/load-json-file": {
"version": "4.0.0",
@ -6568,7 +6548,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
"dev": true,
"dependencies": {
"callsites": "^3.0.0"
},
@ -6580,7 +6559,6 @@
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
"integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
"dev": true,
"dependencies": {
"@babel/code-frame": "^7.0.0",
"error-ex": "^1.3.1",
@ -6638,7 +6616,6 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
"dev": true,
"engines": {
"node": ">=8"
}
@ -8344,7 +8321,6 @@
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
"dev": true,
"engines": {
"node": ">= 6"
}
@ -8492,6 +8468,7 @@
"hasInstallScript": true,
"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",
@ -8550,7 +8527,6 @@
"version": "7.12.11",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz",
"integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==",
"dev": true,
"requires": {
"@babel/highlight": "^7.10.4"
}
@ -8558,14 +8534,12 @@
"@babel/helper-validator-identifier": {
"version": "7.19.1",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
"integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==",
"dev": true
"integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w=="
},
"@babel/highlight": {
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz",
"integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==",
"dev": true,
"requires": {
"@babel/helper-validator-identifier": "^7.18.6",
"chalk": "^2.0.0",
@ -8576,7 +8550,6 @@
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"requires": {
"color-convert": "^1.9.0"
}
@ -8585,7 +8558,6 @@
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"dev": true,
"requires": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
@ -8596,7 +8568,6 @@
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"dev": true,
"requires": {
"color-name": "1.1.3"
}
@ -8604,26 +8575,22 @@
"color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
"dev": true
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
},
"escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
"dev": true
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="
},
"has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
"dev": true
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="
},
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"requires": {
"has-flag": "^3.0.0"
}
@ -9828,8 +9795,7 @@
"@types/parse-json": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",
"integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==",
"dev": true
"integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA=="
},
"@types/pixelmatch": {
"version": "5.2.4",
@ -10358,8 +10324,7 @@
"callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
"dev": true
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="
},
"camelcase": {
"version": "5.3.1",
@ -10547,7 +10512,6 @@
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz",
"integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==",
"dev": true,
"requires": {
"@types/parse-json": "^4.0.0",
"import-fresh": "^3.2.1",
@ -10718,7 +10682,6 @@
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
"integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
"dev": true,
"requires": {
"is-arrayish": "^0.2.1"
}
@ -11990,7 +11953,6 @@
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
"integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
"dev": true,
"requires": {
"parent-module": "^1.0.0",
"resolve-from": "^4.0.0"
@ -11999,8 +11961,7 @@
"resolve-from": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
"dev": true
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="
}
}
},
@ -12083,8 +12044,7 @@
"is-arrayish": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
"integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
"dev": true
"integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="
},
"is-bigint": {
"version": "1.0.4",
@ -12407,8 +12367,7 @@
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"dev": true
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
"js-yaml": {
"version": "3.13.1",
@ -12429,8 +12388,7 @@
"json-parse-even-better-errors": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
"dev": true
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="
},
"json-schema-traverse": {
"version": "1.0.0",
@ -12507,8 +12465,7 @@
"lines-and-columns": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
"dev": true
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="
},
"load-json-file": {
"version": "4.0.0",
@ -13171,7 +13128,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
"dev": true,
"requires": {
"callsites": "^3.0.0"
}
@ -13180,7 +13136,6 @@
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
"integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.0.0",
"error-ex": "^1.3.1",
@ -13222,8 +13177,7 @@
"path-type": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
"dev": true
"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="
},
"pend": {
"version": "1.2.0",
@ -13343,6 +13297,7 @@
"puppeteer": {
"version": "file:packages/puppeteer",
"requires": {
"cosmiconfig": "7.0.1",
"https-proxy-agent": "5.0.1",
"progress": "2.0.3",
"proxy-from-env": "1.1.0",
@ -14494,8 +14449,7 @@
"yaml": {
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
"dev": true
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg=="
},
"yargs": {
"version": "17.6.0",

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} => {
.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;
}
defaultArgs(): string[] {
throw new Error('Not implemented');
}
/**
/**
* @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;
}
if (!chromePath) {
throw new Error(
`Unable to detect browser executable path for '${channel}' on ${platform}.`
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'];
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};
}
downloadPath =
process.env['PUPPETEER_DOWNLOAD_PATH'] ||
process.env['npm_config_puppeteer_download_path'] ||
process.env['npm_package_config_puppeteer_download_path'];
return ubuntuChromiumPath;
}
const browserFetcher = new BrowserFetcher({
product: product,
path: downloadPath,
product: this.product,
path: this.puppeteer.defaultDownloadPath!,
});
if (!_isPuppeteerCore) {
let revision = process.env['PUPPETEER_BROWSER_REVISION'];
if (product === 'chrome') {
revision ??= process.env['PUPPETEER_CHROMIUM_REVISION'];
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.`
);
}
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);
switch (this.product) {
case 'chrome':
return new ChromeLauncher(preferredRevision, isPuppeteerCore);
default:
throw new Error(`Unknown product: ${product}`);
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.`
);
}
}
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;
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:
this._preferredRevision = PUPPETEER_REVISIONS.chromium;
throw new Error(`Unknown product: ${this.#lastLaunchedProduct}`);
}
this._changedProduct = false;
this.#launcher = createLauncher(
this._preferredRevision,
this._isPuppeteerCore,
this._productName
);
}
return this.#launcher;
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'))) {
// 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');
}
// 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();
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';

View File

@ -0,0 +1,8 @@
const {join} = require('path');
/**
* @type {import("puppeteer").PuppeteerConfiguration}
*/
module.exports = {
cacheDirectory: join(__dirname, '.cache', 'puppeteer'),
};

View File

@ -51,6 +51,7 @@ export interface DescribeInstallationOptions {
* This should be idempotent.
*/
env?: ((cwd: string) => NodeJS.ProcessEnv) | NodeJS.ProcessEnv;
before?: (cwd: string) => Promise<void>;
}
export interface DescribeInstallationContext {
@ -92,6 +93,10 @@ export const describeInstallation = (
};
}
env = {...process.env, ...getEnv(sandbox)};
if (options.before) {
await options.before(sandbox);
}
});
after(async () => {

View File

@ -0,0 +1,43 @@
/**
* Copyright 2022 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import assert from 'assert';
import {readdir, writeFile} from 'fs/promises';
import {join} from 'path';
import {describeInstallation} from './describeInstallation.js';
import {readAsset} from './util.js';
describeInstallation(
'`puppeteer` with configuration',
{
dependencies: ['puppeteer-core', 'puppeteer'],
before: async cwd => {
await writeFile(
join(cwd, '.puppeteerrc.cjs'),
await readAsset('puppeteer', 'configuration', '.puppeteerrc.cjs')
);
},
},
({itEvaluates}) => {
itEvaluates('properly', async cwd => {
const files = await readdir(join(cwd, '.cache', 'puppeteer'));
assert.equal(files.length, 1);
assert.equal(files[0], 'chrome');
return readAsset('puppeteer', 'basic.js');
});
}
);

View File

@ -20,7 +20,6 @@ import os from 'os';
import path from 'path';
import {BrowserFetcher, TimeoutError} from 'puppeteer';
import {Page} from 'puppeteer-core/internal/api/Page.js';
import {Product} from 'puppeteer-core/internal/common/Product.js';
import rimraf from 'rimraf';
import sinon from 'sinon';
import {TLSSocket} from 'tls';
@ -259,7 +258,8 @@ describe('Launcher specs', function () {
const testTmpDir = await fs.promises.mkdtemp(
path.join(os.tmpdir(), 'puppeteer_test_chrome_profile-')
);
process.env['PUPPETEER_TMP_DIR'] = testTmpDir;
const oldTmpDir = puppeteer.configuration.temporaryDirectory;
puppeteer.configuration.temporaryDirectory = testTmpDir;
// Path should be empty before starting the browser.
expect(fs.readdirSync(testTmpDir).length).toEqual(0);
@ -277,8 +277,9 @@ describe('Launcher specs', function () {
await browser.close();
// Profile should be deleted after closing the browser
expect(fs.readdirSync(testTmpDir).length).toEqual(0);
// Restore env var
process.env['PUPPETEER_TMP_DIR'] = '';
puppeteer.configuration.temporaryDirectory = oldTmpDir;
});
it('userDataDir option restores preferences', async () => {
const {defaultBrowserOptions, puppeteer} = getTestState();
@ -624,21 +625,6 @@ describe('Launcher specs', function () {
});
describe('Puppeteer.launch', function () {
let productName!: Product;
before(async () => {
const {puppeteer} = getTestState();
productName = puppeteer._productName!;
});
after(async () => {
const {puppeteer} = getTestState();
// @ts-expect-error launcher is a private property that users can't
// touch, but for testing purposes we need to reset it.
puppeteer._lazyLauncher = undefined;
puppeteer._productName = productName;
});
itOnlyRegularInstall('should be able to launch Chrome', async () => {
const {puppeteer} = getTestState();
const browser = await puppeteer.launch({product: 'chrome'});
@ -890,26 +876,29 @@ describe('Launcher specs', function () {
const executablePath = puppeteer.executablePath('chrome');
expect(executablePath).toBeTruthy();
});
describe('when PUPPETEER_EXECUTABLE_PATH is set', () => {
describe('when executable path is configured', () => {
const sandbox = sinon.createSandbox();
beforeEach(() => {
process.env['PUPPETEER_EXECUTABLE_PATH'] = '';
const {puppeteer} = getTestState();
sandbox
.stub(process.env, 'PUPPETEER_EXECUTABLE_PATH')
.stub(puppeteer.configuration, 'executablePath')
.value('SOME_CUSTOM_EXECUTABLE');
});
afterEach(() => {
return sandbox.restore();
sandbox.restore();
});
it('its value is returned', async () => {
it('its value is used', async () => {
const {puppeteer} = getTestState();
const executablePath = puppeteer.executablePath();
expect(executablePath).toEqual('SOME_CUSTOM_EXECUTABLE');
try {
puppeteer.executablePath();
} catch (error) {
expect((error as Error).message).toContain(
'SOME_CUSTOM_EXECUTABLE'
);
}
});
});
@ -930,26 +919,29 @@ describe('Launcher specs', function () {
osArchStub.restore();
fsExistsStub.restore();
});
describe('and PUPPETEER_EXECUTABLE_PATH is set', () => {
describe('and the executable path is configured', () => {
const sandbox = sinon.createSandbox();
beforeEach(() => {
process.env['PUPPETEER_EXECUTABLE_PATH'] = '';
const {puppeteer} = getTestState();
sandbox
.stub(process.env, 'PUPPETEER_EXECUTABLE_PATH')
.stub(puppeteer.configuration, 'executablePath')
.value('SOME_CUSTOM_EXECUTABLE');
});
afterEach(() => {
return sandbox.restore();
sandbox.restore();
});
it('its value is returned', async () => {
it('its value is used', async () => {
const {puppeteer} = getTestState();
const executablePath = puppeteer.executablePath();
expect(executablePath).toEqual('SOME_CUSTOM_EXECUTABLE');
try {
puppeteer.executablePath();
} catch (error) {
expect((error as Error).message).toContain(
'SOME_CUSTOM_EXECUTABLE'
);
}
});
});
});
@ -961,9 +953,9 @@ describe('Launcher specs', function () {
const fsExistsStub = sinon.stub(fs, 'existsSync');
fsExistsStub.withArgs('/usr/bin/chromium-browser').returns(false);
const executablePath = puppeteer.executablePath();
expect(executablePath).not.toEqual('/usr/bin/chromium-browser');
expect(() => {
return puppeteer.executablePath();
}).toThrowError();
osPlatformStub.restore();
osArchStub.restore();

View File

@ -101,14 +101,6 @@ const defaultBrowserOptions = Object.assign(
`WARN: running ${product} tests with ${defaultBrowserOptions.executablePath}`
);
} else {
// TODO(jackfranklin): declare updateRevision in some form for the Firefox
// launcher.
if (product === 'firefox') {
// @ts-expect-error _updateRevision is defined on the FF launcher
// but not the Chrome one. The types need tidying so that TS can infer that
// properly and not error here.
await puppeteer._launcher._updateRevision();
}
const executablePath = puppeteer.executablePath();
if (!fs.existsSync(executablePath)) {
throw new Error(