puppeteer-core
Advanced tools
Comparing version 2.0.0 to 2.1.0
@@ -41,3 +41,3 @@ <!-- gen:toc --> | ||
```bash | ||
git clone https://github.com/GoogleChrome/puppeteer | ||
git clone https://github.com/puppeteer/puppeteer | ||
cd puppeteer | ||
@@ -67,3 +67,3 @@ ``` | ||
- Coding style is fully defined in [.eslintrc](https://github.com/GoogleChrome/puppeteer/blob/master/.eslintrc.js) | ||
- Coding style is fully defined in [.eslintrc](https://github.com/puppeteer/puppeteer/blob/master/.eslintrc.js) | ||
- Code should be annotated with [closure annotations](https://github.com/google/closure-compiler/wiki/Annotating-JavaScript-for-the-Closure-Compiler). | ||
@@ -129,3 +129,3 @@ - Comments should be generally avoided. If the code would not be understood without comments, consider re-writing the code to make it self-explanatory. | ||
All public API should have a descriptive entry in [`docs/api.md`](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md). There's a [documentation linter](https://github.com/GoogleChrome/puppeteer/tree/master/utils/doclint) which makes sure documentation is aligned with the codebase. | ||
All public API should have a descriptive entry in [`docs/api.md`](https://github.com/puppeteer/puppeteer/blob/master/docs/api.md). There's a [documentation linter](https://github.com/puppeteer/puppeteer/tree/master/utils/doclint) which makes sure documentation is aligned with the codebase. | ||
@@ -154,4 +154,4 @@ To run the documentation linter, use: | ||
Puppeteer tests are located in [`test/test.js`](https://github.com/GoogleChrome/puppeteer/blob/master/test/test.js) | ||
and are written with a [TestRunner](https://github.com/GoogleChrome/puppeteer/tree/master/utils/testrunner) framework. | ||
Puppeteer tests are located in [`test/test.js`](https://github.com/puppeteer/puppeteer/blob/master/test/test.js) | ||
and are written with a [TestRunner](https://github.com/puppeteer/puppeteer/tree/master/utils/testrunner) framework. | ||
Despite being named 'unit', these are integration tests, making sure public API methods and events work as expected. | ||
@@ -206,6 +206,6 @@ | ||
- To run tests with custom Chromium executable: | ||
- To run tests with custom browser executable: | ||
```bash | ||
CHROME=<path-to-executable> npm run unit | ||
BINARY=<path-to-executable> npm run unit | ||
``` | ||
@@ -219,2 +219,9 @@ | ||
- To run tests with additional Launcher options: | ||
```bash | ||
EXTRA_LAUNCH_OPTIONS='{"args": ["--user-data-dir=some/path"], "handleSIGINT": true}' npm run unit | ||
``` | ||
- To debug a test, "focus" a test first and then run: | ||
@@ -247,16 +254,19 @@ | ||
1. Source Code: mark a release. | ||
1. Bump `package.json` version following the SEMVER rules, run `npm run doc` to update the docs accordingly, and send a PR titled `'chore: mark version vXXX.YYY.ZZZ'` ([example](https://github.com/GoogleChrome/puppeteer/commit/808bf8e5582482a1d849ff22a51e52024810905c)). | ||
2. Make sure the PR passes **all checks**. | ||
- **WHY**: there are linters in place that help to avoid unnecessary errors, e.g. [like this](https://github.com/GoogleChrome/puppeteer/pull/2446) | ||
3. Merge the PR. | ||
4. Once merged, publish the release notes using [GitHub's "draft new release tag" option](https://github.com/GoogleChrome/puppeteer/releases/new). | ||
1. Bump `package.json` version following the SEMVER rules. | ||
2. Run `npm run doc` to update the docs accordingly. | ||
3. Update the “Releases per Chromium Version” list in [`docs/api.md`](https://github.com/puppeteer/puppeteer/blob/master/docs/api.md) to include the new version. | ||
4. Send a PR titled `'chore: mark version vXXX.YYY.ZZZ'` ([example](https://github.com/puppeteer/puppeteer/pull/5078)). | ||
5. Make sure the PR passes **all checks**. | ||
- **WHY**: there are linters in place that help to avoid unnecessary errors, e.g. [like this](https://github.com/puppeteer/puppeteer/pull/2446) | ||
6. Merge the PR. | ||
7. Once merged, publish the release notes using [GitHub's “draft new release tag” option](https://github.com/puppeteer/puppeteer/releases/new). | ||
- **NOTE**: tag names are prefixed with `'v'`, e.g. for version `1.4.0` the tag is `v1.4.0`. | ||
- For the "raw notes" section, use `git log --pretty="%h - %s" v1.19.0..HEAD`. | ||
5. Update the “Releases per Chromium Version” list in [`docs/api.md`](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md) to include the new version. | ||
- For the “raw notes” section, use `git log --pretty="%h - %s" v2.0.0..HEAD`. | ||
2. Publish `puppeteer` to npm. | ||
1. On your local machine, pull from [upstream](https://github.com/GoogleChrome/puppeteer) and make sure the last commit is the one just merged. | ||
1. On your local machine, pull from [upstream](https://github.com/puppeteer/puppeteer) and make sure the last commit is the one just merged. | ||
2. Run `git status` and make sure there are no untracked files. | ||
- **WHY**: this is to avoid adding unnecessary files to the npm package. | ||
3. Run [`npx pkgfiles`](https://www.npmjs.com/package/pkgfiles) to make sure you don't publish anything unnecessary. | ||
4. Run `npm publish`. This publishes the `puppeteer` package. | ||
3. Run `npm install` to make sure the latest `lib/protocol.d.ts` is generated. | ||
4. Run [`npx pkgfiles`](https://www.npmjs.com/package/pkgfiles) to make sure you don't publish anything unnecessary. | ||
5. Run `npm publish`. This publishes the `puppeteer` package. | ||
3. Publish `puppeteer-core` to npm. | ||
@@ -267,4 +277,3 @@ 1. Run `./utils/prepare_puppeteer_core.js`. The script changes the name inside `package.json` to `puppeteer-core`. | ||
4. Source Code: mark post-release. | ||
1. Bump `package.json` version to `-post` version and send a PR titled `'chore: bump version to vXXX.YYY.ZZZ-post'` ([example](https://github.com/GoogleChrome/puppeteer/commit/d02440d1eac98028e29f4e1cf55413062a259156)) | ||
- **NOTE**: make sure to update the "released APIs" section in the top of `docs/api.md` by running `npm run doc`. | ||
1. Bump `package.json` version to `-post` version, run `npm run doc` to update the “released APIs” section at the top of `docs/api.md` accordingly, and send a PR titled `'chore: bump version to vXXX.YYY.ZZZ-post'` ([example](https://github.com/puppeteer/puppeteer/commit/d02440d1eac98028e29f4e1cf55413062a259156)) | ||
- **NOTE**: no other commits should be landed in-between release commit and bump commit. | ||
@@ -271,0 +280,0 @@ |
@@ -31,2 +31,9 @@ /** | ||
module.exports = new Puppeteer(__dirname, preferredRevision, isPuppeteerCore); | ||
const puppeteer = new Puppeteer(__dirname, preferredRevision, isPuppeteerCore); | ||
// The introspection in `Helper.installAsyncStackHooks` references `Puppeteer._launcher` | ||
// before the Puppeteer ctor is called, such that an invalid Launcher is selected at import, | ||
// so we reset it. | ||
puppeteer._lazyLauncher = undefined; | ||
module.exports = puppeteer; |
@@ -186,2 +186,14 @@ /** | ||
/** | ||
* @param {Protocol.DOM.BackendNodeId} backendNodeId | ||
* @return {Promise<Puppeteer.ElementHandle>} | ||
*/ | ||
async _adoptBackendNodeId(backendNodeId) { | ||
const {object} = await this._client.send('DOM.resolveNode', { | ||
backendNodeId: backendNodeId, | ||
executionContextId: this._contextId, | ||
}); | ||
return /** @type {Puppeteer.ElementHandle}*/(createJSHandle(this, object)); | ||
} | ||
/** | ||
* @param {Puppeteer.ElementHandle} elementHandle | ||
@@ -196,7 +208,3 @@ * @return {Promise<Puppeteer.ElementHandle>} | ||
}); | ||
const {object} = await this._client.send('DOM.resolveNode', { | ||
backendNodeId: nodeInfo.node.backendNodeId, | ||
executionContextId: this._contextId, | ||
}); | ||
return /** @type {Puppeteer.ElementHandle}*/(createJSHandle(this, object)); | ||
return this._adoptBackendNodeId(nodeInfo.node.backendNodeId); | ||
} | ||
@@ -203,0 +211,0 @@ } |
@@ -140,3 +140,3 @@ /** | ||
listener.emitter.removeListener(listener.eventName, listener.handler); | ||
listeners.splice(0, listeners.length); | ||
listeners.length = 0; | ||
} | ||
@@ -143,0 +143,0 @@ |
@@ -18,3 +18,2 @@ /** | ||
const {helper, assert, debugError} = require('./helper'); | ||
const path = require('path'); | ||
@@ -306,4 +305,4 @@ function createJSHandle(context, remoteObject) { | ||
} | ||
element.dispatchEvent(new Event('input', { 'bubbles': true })); | ||
element.dispatchEvent(new Event('change', { 'bubbles': true })); | ||
element.dispatchEvent(new Event('input', { bubbles: true })); | ||
element.dispatchEvent(new Event('change', { bubbles: true })); | ||
return options.filter(option => option.selected).map(option => option.value); | ||
@@ -317,5 +316,33 @@ }, values); | ||
async uploadFile(...filePaths) { | ||
const files = filePaths.map(filePath => path.resolve(filePath)); | ||
const objectId = this._remoteObject.objectId; | ||
await this._client.send('DOM.setFileInputFiles', { objectId, files }); | ||
const isMultiple = await this.evaluate(element => element.multiple); | ||
assert(filePaths.length <= 1 || isMultiple, 'Multiple file uploads only work with <input type=file multiple>'); | ||
// These imports are only needed for `uploadFile`, so keep them | ||
// scoped here to avoid paying the cost unnecessarily. | ||
const path = require('path'); | ||
const mime = require('mime-types'); | ||
const fs = require('fs'); | ||
const readFileAsync = helper.promisify(fs.readFile); | ||
const promises = filePaths.map(filePath => readFileAsync(filePath)); | ||
const files = []; | ||
for (let i = 0; i < filePaths.length; i++) { | ||
const buffer = await promises[i]; | ||
const filePath = path.basename(filePaths[i]); | ||
const file = { | ||
name: filePath, | ||
content: buffer.toString('base64'), | ||
mimeType: mime.lookup(filePath), | ||
}; | ||
files.push(file); | ||
} | ||
await this.evaluateHandle(async(element, files) => { | ||
const dt = new DataTransfer(); | ||
for (const item of files) { | ||
const response = await fetch(`data:${item.mimeType};base64,${item.content}`); | ||
const file = new File([await response.blob()], item.name); | ||
dt.items.add(file); | ||
} | ||
element.files = dt.files; | ||
element.dispatchEvent(new Event('input', { bubbles: true })); | ||
}, files); | ||
} | ||
@@ -322,0 +349,0 @@ |
@@ -29,2 +29,3 @@ /** | ||
const {helper, assert, debugError} = require('./helper'); | ||
const debugLauncher = require('debug')(`puppeteer:launcher`); | ||
const {TimeoutError} = require('./Errors'); | ||
@@ -36,34 +37,149 @@ const WebSocketTransport = require('./WebSocketTransport'); | ||
const removeFolderAsync = helper.promisify(removeFolder); | ||
const writeFileAsync = helper.promisify(fs.writeFile); | ||
const CHROME_PROFILE_PATH = path.join(os.tmpdir(), 'puppeteer_dev_profile-'); | ||
class BrowserRunner { | ||
const DEFAULT_ARGS = [ | ||
'--disable-background-networking', | ||
'--enable-features=NetworkService,NetworkServiceInProcess', | ||
'--disable-background-timer-throttling', | ||
'--disable-backgrounding-occluded-windows', | ||
'--disable-breakpad', | ||
'--disable-client-side-phishing-detection', | ||
'--disable-component-extensions-with-background-pages', | ||
'--disable-default-apps', | ||
'--disable-dev-shm-usage', | ||
'--disable-extensions', | ||
// BlinkGenPropertyTrees disabled due to crbug.com/937609 | ||
'--disable-features=TranslateUI,BlinkGenPropertyTrees', | ||
'--disable-hang-monitor', | ||
'--disable-ipc-flooding-protection', | ||
'--disable-popup-blocking', | ||
'--disable-prompt-on-repost', | ||
'--disable-renderer-backgrounding', | ||
'--disable-sync', | ||
'--force-color-profile=srgb', | ||
'--metrics-recording-only', | ||
'--no-first-run', | ||
'--enable-automation', | ||
'--password-store=basic', | ||
'--use-mock-keychain', | ||
]; | ||
/** | ||
* @param {string} executablePath | ||
* @param {!Array<string>} processArguments | ||
* @param {string=} tempDirectory | ||
*/ | ||
constructor(executablePath, processArguments, tempDirectory) { | ||
this._executablePath = executablePath; | ||
this._processArguments = processArguments; | ||
this._tempDirectory = tempDirectory; | ||
this.proc = null; | ||
this.connection = null; | ||
this._closed = true; | ||
this._listeners = []; | ||
} | ||
class Launcher { | ||
/** | ||
* @param {!(Launcher.LaunchOptions)=} options | ||
*/ | ||
start(options = {}) { | ||
const { | ||
handleSIGINT, | ||
handleSIGTERM, | ||
handleSIGHUP, | ||
dumpio, | ||
env, | ||
pipe | ||
} = options; | ||
/** @type {!Array<"ignore"|"pipe">} */ | ||
let stdio = ['pipe', 'pipe', 'pipe']; | ||
if (pipe) { | ||
if (dumpio) | ||
stdio = ['ignore', 'pipe', 'pipe', 'pipe', 'pipe']; | ||
else | ||
stdio = ['ignore', 'ignore', 'ignore', 'pipe', 'pipe']; | ||
} | ||
assert(!this.proc, 'This process has previously been started.'); | ||
debugLauncher(`Calling ${this._executablePath} ${this._processArguments.join(' ')}`); | ||
this.proc = childProcess.spawn( | ||
this._executablePath, | ||
this._processArguments, | ||
{ | ||
// On non-windows platforms, `detached: true` makes child process a leader of a new | ||
// process group, making it possible to kill child process tree with `.kill(-pid)` command. | ||
// @see https://nodejs.org/api/child_process.html#child_process_options_detached | ||
detached: process.platform !== 'win32', | ||
env, | ||
stdio | ||
} | ||
); | ||
if (dumpio) { | ||
this.proc.stderr.pipe(process.stderr); | ||
this.proc.stdout.pipe(process.stdout); | ||
} | ||
this._closed = false; | ||
this._processClosing = new Promise((fulfill, reject) => { | ||
this.proc.once('exit', () => { | ||
this._closed = true; | ||
// Cleanup as processes exit. | ||
if (this._tempDirectory) { | ||
removeFolderAsync(this._tempDirectory) | ||
.then(() => fulfill()) | ||
.catch(err => console.error(err)); | ||
} else { | ||
fulfill(); | ||
} | ||
}); | ||
}); | ||
this._listeners = [ helper.addEventListener(process, 'exit', this.kill.bind(this)) ]; | ||
if (handleSIGINT) | ||
this._listeners.push(helper.addEventListener(process, 'SIGINT', () => { this.kill(); process.exit(130); })); | ||
if (handleSIGTERM) | ||
this._listeners.push(helper.addEventListener(process, 'SIGTERM', this.close.bind(this))); | ||
if (handleSIGHUP) | ||
this._listeners.push(helper.addEventListener(process, 'SIGHUP', this.close.bind(this))); | ||
} | ||
/** | ||
* @return {Promise} | ||
*/ | ||
close() { | ||
if (this._closed) | ||
return Promise.resolve(); | ||
helper.removeEventListeners(this._listeners); | ||
if (this._tempDirectory) { | ||
this.kill(); | ||
} else if (this.connection) { | ||
// Attempt to close the browser gracefully | ||
this.connection.send('Browser.close').catch(error => { | ||
debugError(error); | ||
this.kill(); | ||
}); | ||
} | ||
return this._processClosing; | ||
} | ||
// This function has to be sync to be used as 'exit' event handler. | ||
kill() { | ||
helper.removeEventListeners(this._listeners); | ||
if (this.proc && this.proc.pid && !this.proc.killed && !this._closed) { | ||
try { | ||
if (process.platform === 'win32') | ||
childProcess.execSync(`taskkill /pid ${this.proc.pid} /T /F`); | ||
else | ||
process.kill(-this.proc.pid, 'SIGKILL'); | ||
} catch (error) { | ||
// the process might have already stopped | ||
} | ||
} | ||
// Attempt to remove temporary profile directory to avoid littering. | ||
try { | ||
removeFolder.sync(this._tempDirectory); | ||
} catch (error) { } | ||
} | ||
/** | ||
* @param {!({usePipe?: boolean, timeout: number, slowMo: number, preferredRevision: string})} options | ||
* | ||
* @return {!Promise<!Connection>} | ||
*/ | ||
async setupConnection(options) { | ||
const { | ||
usePipe, | ||
timeout, | ||
slowMo, | ||
preferredRevision | ||
} = options; | ||
if (!usePipe) { | ||
const browserWSEndpoint = await waitForWSEndpoint(this.proc, timeout, preferredRevision); | ||
const transport = await WebSocketTransport.create(browserWSEndpoint); | ||
this.connection = new Connection(browserWSEndpoint, transport, slowMo); | ||
} else { | ||
const transport = new PipeTransport(/** @type {!NodeJS.WritableStream} */(this.proc.stdio[3]), /** @type {!NodeJS.ReadableStream} */ (this.proc.stdio[4])); | ||
this.connection = new Connection('', transport, slowMo); | ||
} | ||
return this.connection; | ||
} | ||
} | ||
/** | ||
* @implements {!Puppeteer.ProductLauncher} | ||
*/ | ||
class ChromeLauncher { | ||
/** | ||
* @param {string} projectRoot | ||
@@ -100,2 +216,3 @@ * @param {string} preferredRevision | ||
const profilePath = path.join(os.tmpdir(), 'puppeteer_dev_chrome_profile-'); | ||
const chromeArguments = []; | ||
@@ -105,3 +222,3 @@ if (!ignoreDefaultArgs) | ||
else if (Array.isArray(ignoreDefaultArgs)) | ||
chromeArguments.push(...this.defaultArgs(options).filter(arg => ignoreDefaultArgs.indexOf(arg) === -1)); | ||
chromeArguments.push(...this.defaultArgs(options).filter(arg => !ignoreDefaultArgs.includes(arg))); | ||
else | ||
@@ -115,3 +232,3 @@ chromeArguments.push(...args); | ||
if (!chromeArguments.some(arg => arg.startsWith('--user-data-dir'))) { | ||
temporaryUserDataDir = await mkdtempAsync(CHROME_PROFILE_PATH); | ||
temporaryUserDataDir = await mkdtempAsync(profilePath); | ||
chromeArguments.push(`--user-data-dir=${temporaryUserDataDir}`); | ||
@@ -122,3 +239,3 @@ } | ||
if (!executablePath) { | ||
const {missingText, executablePath} = this._resolveExecutablePath(); | ||
const {missingText, executablePath} = resolveExecutablePath(this); | ||
if (missingText) | ||
@@ -130,105 +247,14 @@ throw new Error(missingText); | ||
const usePipe = chromeArguments.includes('--remote-debugging-pipe'); | ||
/** @type {!Array<"ignore"|"pipe">} */ | ||
let stdio = ['pipe', 'pipe', 'pipe']; | ||
if (usePipe) { | ||
if (dumpio) | ||
stdio = ['ignore', 'pipe', 'pipe', 'pipe', 'pipe']; | ||
else | ||
stdio = ['ignore', 'ignore', 'ignore', 'pipe', 'pipe']; | ||
} | ||
const chromeProcess = childProcess.spawn( | ||
chromeExecutable, | ||
chromeArguments, | ||
{ | ||
// On non-windows platforms, `detached: true` makes child process a leader of a new | ||
// process group, making it possible to kill child process tree with `.kill(-pid)` command. | ||
// @see https://nodejs.org/api/child_process.html#child_process_options_detached | ||
detached: process.platform !== 'win32', | ||
env, | ||
stdio | ||
} | ||
); | ||
const runner = new BrowserRunner(chromeExecutable, chromeArguments, temporaryUserDataDir); | ||
runner.start({handleSIGHUP, handleSIGTERM, handleSIGINT, dumpio, env, pipe: usePipe}); | ||
if (dumpio) { | ||
chromeProcess.stderr.pipe(process.stderr); | ||
chromeProcess.stdout.pipe(process.stdout); | ||
} | ||
let chromeClosed = false; | ||
const waitForChromeToClose = new Promise((fulfill, reject) => { | ||
chromeProcess.once('exit', () => { | ||
chromeClosed = true; | ||
// Cleanup as processes exit. | ||
if (temporaryUserDataDir) { | ||
removeFolderAsync(temporaryUserDataDir) | ||
.then(() => fulfill()) | ||
.catch(err => console.error(err)); | ||
} else { | ||
fulfill(); | ||
} | ||
}); | ||
}); | ||
const listeners = [ helper.addEventListener(process, 'exit', killChrome) ]; | ||
if (handleSIGINT) | ||
listeners.push(helper.addEventListener(process, 'SIGINT', () => { killChrome(); process.exit(130); })); | ||
if (handleSIGTERM) | ||
listeners.push(helper.addEventListener(process, 'SIGTERM', gracefullyCloseChrome)); | ||
if (handleSIGHUP) | ||
listeners.push(helper.addEventListener(process, 'SIGHUP', gracefullyCloseChrome)); | ||
/** @type {?Connection} */ | ||
let connection = null; | ||
try { | ||
if (!usePipe) { | ||
const browserWSEndpoint = await waitForWSEndpoint(chromeProcess, timeout, this._preferredRevision); | ||
const transport = await WebSocketTransport.create(browserWSEndpoint); | ||
connection = new Connection(browserWSEndpoint, transport, slowMo); | ||
} else { | ||
const transport = new PipeTransport(/** @type {!NodeJS.WritableStream} */(chromeProcess.stdio[3]), /** @type {!NodeJS.ReadableStream} */ (chromeProcess.stdio[4])); | ||
connection = new Connection('', transport, slowMo); | ||
} | ||
const browser = await Browser.create(connection, [], ignoreHTTPSErrors, defaultViewport, chromeProcess, gracefullyCloseChrome); | ||
const connection = await runner.setupConnection({usePipe, timeout, slowMo, preferredRevision: this._preferredRevision}); | ||
const browser = await Browser.create(connection, [], ignoreHTTPSErrors, defaultViewport, runner.proc, runner.close.bind(runner)); | ||
await browser.waitForTarget(t => t.type() === 'page'); | ||
return browser; | ||
} catch (e) { | ||
killChrome(); | ||
throw e; | ||
} catch (error) { | ||
runner.kill(); | ||
throw error; | ||
} | ||
/** | ||
* @return {Promise} | ||
*/ | ||
function gracefullyCloseChrome() { | ||
helper.removeEventListeners(listeners); | ||
if (temporaryUserDataDir) { | ||
killChrome(); | ||
} else if (connection) { | ||
// Attempt to close chrome gracefully | ||
connection.send('Browser.close').catch(error => { | ||
debugError(error); | ||
killChrome(); | ||
}); | ||
} | ||
return waitForChromeToClose; | ||
} | ||
// This method has to be sync to be used as 'exit' event handler. | ||
function killChrome() { | ||
helper.removeEventListeners(listeners); | ||
if (chromeProcess.pid && !chromeProcess.killed && !chromeClosed) { | ||
// Force kill chrome. | ||
try { | ||
if (process.platform === 'win32') | ||
childProcess.execSync(`taskkill /pid ${chromeProcess.pid} /T /F`); | ||
else | ||
process.kill(-chromeProcess.pid, 'SIGKILL'); | ||
} catch (e) { | ||
// the process might have already stopped | ||
} | ||
} | ||
// Attempt to remove temporary profile directory to avoid littering. | ||
try { | ||
removeFolder.sync(temporaryUserDataDir); | ||
} catch (e) { } | ||
} | ||
} | ||
@@ -241,2 +267,27 @@ | ||
defaultArgs(options = {}) { | ||
const chromeArguments = [ | ||
'--disable-background-networking', | ||
'--enable-features=NetworkService,NetworkServiceInProcess', | ||
'--disable-background-timer-throttling', | ||
'--disable-backgrounding-occluded-windows', | ||
'--disable-breakpad', | ||
'--disable-client-side-phishing-detection', | ||
'--disable-component-extensions-with-background-pages', | ||
'--disable-default-apps', | ||
'--disable-dev-shm-usage', | ||
'--disable-extensions', | ||
'--disable-features=TranslateUI', | ||
'--disable-hang-monitor', | ||
'--disable-ipc-flooding-protection', | ||
'--disable-popup-blocking', | ||
'--disable-prompt-on-repost', | ||
'--disable-renderer-backgrounding', | ||
'--disable-sync', | ||
'--force-color-profile=srgb', | ||
'--metrics-recording-only', | ||
'--no-first-run', | ||
'--enable-automation', | ||
'--password-store=basic', | ||
'--use-mock-keychain', | ||
]; | ||
const { | ||
@@ -248,3 +299,2 @@ devtools = false, | ||
} = options; | ||
const chromeArguments = [...DEFAULT_ARGS]; | ||
if (userDataDir) | ||
@@ -271,6 +321,13 @@ chromeArguments.push(`--user-data-dir=${userDataDir}`); | ||
executablePath() { | ||
return this._resolveExecutablePath().executablePath; | ||
return resolveExecutablePath(this).executablePath; | ||
} | ||
/** | ||
* @return {string} | ||
*/ | ||
get product() { | ||
return 'chrome'; | ||
} | ||
/** | ||
* @param {!(Launcher.BrowserOptions & {browserWSEndpoint?: string, browserURL?: string, transport?: !Puppeteer.ConnectionTransport})} options | ||
@@ -307,32 +364,377 @@ * @return {!Promise<!Browser>} | ||
} | ||
/** | ||
* @implements {!Puppeteer.ProductLauncher} | ||
*/ | ||
class FirefoxLauncher { | ||
/** | ||
* @return {{executablePath: string, missingText: ?string}} | ||
* @param {string} projectRoot | ||
* @param {string} preferredRevision | ||
* @param {boolean} isPuppeteerCore | ||
*/ | ||
_resolveExecutablePath() { | ||
// puppeteer-core doesn't take into account PUPPETEER_* env variables. | ||
if (!this._isPuppeteerCore) { | ||
const executablePath = process.env.PUPPETEER_EXECUTABLE_PATH || process.env.npm_config_puppeteer_executable_path || process.env.npm_package_config_puppeteer_executable_path; | ||
if (executablePath) { | ||
const missingText = !fs.existsSync(executablePath) ? 'Tried to use PUPPETEER_EXECUTABLE_PATH env variable to launch browser but did not find any executable at: ' + executablePath : null; | ||
return { executablePath, missingText }; | ||
} | ||
constructor(projectRoot, preferredRevision, isPuppeteerCore) { | ||
this._projectRoot = projectRoot; | ||
this._preferredRevision = preferredRevision; | ||
this._isPuppeteerCore = isPuppeteerCore; | ||
} | ||
/** | ||
* @param {!(Launcher.LaunchOptions & Launcher.ChromeArgOptions & Launcher.BrowserOptions & {extraPrefsFirefox?: !object})=} options | ||
* @return {!Promise<!Browser>} | ||
*/ | ||
async launch(options = {}) { | ||
const { | ||
ignoreDefaultArgs = false, | ||
args = [], | ||
dumpio = false, | ||
executablePath = null, | ||
pipe = false, | ||
env = process.env, | ||
handleSIGINT = true, | ||
handleSIGTERM = true, | ||
handleSIGHUP = true, | ||
ignoreHTTPSErrors = false, | ||
defaultViewport = {width: 800, height: 600}, | ||
slowMo = 0, | ||
timeout = 30000, | ||
extraPrefsFirefox = {} | ||
} = options; | ||
const firefoxArguments = []; | ||
if (!ignoreDefaultArgs) | ||
firefoxArguments.push(...this.defaultArgs(options)); | ||
else if (Array.isArray(ignoreDefaultArgs)) | ||
firefoxArguments.push(...this.defaultArgs(options).filter(arg => !ignoreDefaultArgs.includes(arg))); | ||
else | ||
firefoxArguments.push(...args); | ||
let temporaryUserDataDir = null; | ||
if (!firefoxArguments.includes('-profile') && !firefoxArguments.includes('--profile')) { | ||
temporaryUserDataDir = await this._createProfile(extraPrefsFirefox); | ||
firefoxArguments.push('--profile'); | ||
firefoxArguments.push(temporaryUserDataDir); | ||
} | ||
const browserFetcher = new BrowserFetcher(this._projectRoot); | ||
if (!this._isPuppeteerCore) { | ||
const revision = process.env['PUPPETEER_CHROMIUM_REVISION']; | ||
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 : null; | ||
return {executablePath: revisionInfo.executablePath, missingText}; | ||
} | ||
let executable = executablePath; | ||
if (!executablePath) { | ||
const {missingText, executablePath} = resolveExecutablePath(this); | ||
if (missingText) | ||
throw new Error(missingText); | ||
executable = executablePath; | ||
} | ||
const revisionInfo = browserFetcher.revisionInfo(this._preferredRevision); | ||
const missingText = !revisionInfo.local ? `Chromium revision is not downloaded. Run "npm install" or "yarn install"` : null; | ||
return {executablePath: revisionInfo.executablePath, missingText}; | ||
const runner = new BrowserRunner(executable, firefoxArguments, temporaryUserDataDir); | ||
runner.start({handleSIGHUP, handleSIGTERM, handleSIGINT, dumpio, env, pipe}); | ||
try { | ||
const connection = await runner.setupConnection({usePipe: pipe, timeout, slowMo, preferredRevision: this._preferredRevision}); | ||
const browser = await Browser.create(connection, [], ignoreHTTPSErrors, defaultViewport, runner.proc, runner.close.bind(runner)); | ||
await browser.waitForTarget(t => t.type() === 'page'); | ||
return browser; | ||
} catch (error) { | ||
runner.kill(); | ||
throw error; | ||
} | ||
} | ||
/** | ||
* @param {!(Launcher.BrowserOptions & {browserWSEndpoint?: string, browserURL?: string, transport?: !Puppeteer.ConnectionTransport})} options | ||
* @return {!Promise<!Browser>} | ||
*/ | ||
async connect(options) { | ||
const { | ||
browserWSEndpoint, | ||
browserURL, | ||
ignoreHTTPSErrors = false, | ||
defaultViewport = {width: 800, height: 600}, | ||
transport, | ||
slowMo = 0, | ||
} = options; | ||
assert(Number(!!browserWSEndpoint) + Number(!!browserURL) + Number(!!transport) === 1, 'Exactly one of browserWSEndpoint, browserURL or transport must be passed to puppeteer.connect'); | ||
let connection = null; | ||
if (transport) { | ||
connection = new Connection('', transport, slowMo); | ||
} else if (browserWSEndpoint) { | ||
const connectionTransport = await WebSocketTransport.create(browserWSEndpoint); | ||
connection = new Connection(browserWSEndpoint, connectionTransport, slowMo); | ||
} else if (browserURL) { | ||
const connectionURL = await getWSEndpoint(browserURL); | ||
const connectionTransport = await WebSocketTransport.create(connectionURL); | ||
connection = new Connection(connectionURL, connectionTransport, slowMo); | ||
} | ||
const {browserContextIds} = await connection.send('Target.getBrowserContexts'); | ||
return Browser.create(connection, browserContextIds, ignoreHTTPSErrors, defaultViewport, null, () => connection.send('Browser.close').catch(debugError)); | ||
} | ||
/** | ||
* @return {string} | ||
*/ | ||
executablePath() { | ||
const executablePath = process.env.PUPPETEER_EXECUTABLE_PATH || process.env.npm_config_puppeteer_executable_path || process.env.npm_package_config_puppeteer_executable_path; | ||
// TODO get resolveExecutablePath working for Firefox | ||
if (!executablePath) | ||
throw new Error('Please set PUPPETEER_EXECUTABLE_PATH to a Firefox binary.'); | ||
return executablePath; | ||
} | ||
/** | ||
* @return {string} | ||
*/ | ||
get product() { | ||
return 'firefox'; | ||
} | ||
/** | ||
* @param {!Launcher.ChromeArgOptions=} options | ||
* @return {!Array<string>} | ||
*/ | ||
defaultArgs(options = {}) { | ||
const firefoxArguments = [ | ||
'--remote-debugging-port=0', | ||
'--no-remote', | ||
'--foreground', | ||
]; | ||
const { | ||
devtools = false, | ||
headless = !devtools, | ||
args = [], | ||
userDataDir = null | ||
} = options; | ||
if (userDataDir) { | ||
firefoxArguments.push('--profile'); | ||
firefoxArguments.push(userDataDir); | ||
} | ||
if (headless) | ||
firefoxArguments.push('--headless'); | ||
if (devtools) | ||
firefoxArguments.push('--devtools'); | ||
if (args.every(arg => arg.startsWith('-'))) | ||
firefoxArguments.push('about:blank'); | ||
firefoxArguments.push(...args); | ||
return firefoxArguments; | ||
} | ||
/** | ||
* @param {!Object=} extraPrefs | ||
* @return {!Promise<string>} | ||
*/ | ||
async _createProfile(extraPrefs) { | ||
const profilePath = await mkdtempAsync(path.join(os.tmpdir(), 'puppeteer_dev_firefox_profile-')); | ||
const prefsJS = []; | ||
const userJS = []; | ||
const server = 'dummy.test'; | ||
const defaultPreferences = { | ||
// Make sure Shield doesn't hit the network. | ||
'app.normandy.api_url': '', | ||
// Disable Firefox old build background check | ||
'app.update.checkInstallTime': false, | ||
// Disable automatically upgrading Firefox | ||
'app.update.disabledForTesting': true, | ||
// Increase the APZ content response timeout to 1 minute | ||
'apz.content_response_timeout': 60000, | ||
// Prevent various error message on the console | ||
// jest-puppeteer asserts that no error message is emitted by the console | ||
'browser.contentblocking.features.standard': '-tp,tpPrivate,cookieBehavior0,-cm,-fp', | ||
// Enable the dump function: which sends messages to the system | ||
// console | ||
// https://bugzilla.mozilla.org/show_bug.cgi?id=1543115 | ||
'browser.dom.window.dump.enabled': true, | ||
// Disable topstories | ||
'browser.newtabpage.activity-stream.feeds.section.topstories': false, | ||
// Always display a blank page | ||
'browser.newtabpage.enabled': false, | ||
// Background thumbnails in particular cause grief: and disabling | ||
// thumbnails in general cannot hurt | ||
'browser.pagethumbnails.capturing_disabled': true, | ||
// Disable safebrowsing components. | ||
'browser.safebrowsing.blockedURIs.enabled': false, | ||
'browser.safebrowsing.downloads.enabled': false, | ||
'browser.safebrowsing.malware.enabled': false, | ||
'browser.safebrowsing.passwords.enabled': false, | ||
'browser.safebrowsing.phishing.enabled': false, | ||
// Disable updates to search engines. | ||
'browser.search.update': false, | ||
// Do not restore the last open set of tabs if the browser has crashed | ||
'browser.sessionstore.resume_from_crash': false, | ||
// Skip check for default browser on startup | ||
'browser.shell.checkDefaultBrowser': false, | ||
// Disable newtabpage | ||
'browser.startup.homepage': 'about:blank', | ||
// Do not redirect user when a milstone upgrade of Firefox is detected | ||
'browser.startup.homepage_override.mstone': 'ignore', | ||
// Start with a blank page about:blank | ||
'browser.startup.page': 0, | ||
// Do not allow background tabs to be zombified on Android: otherwise for | ||
// tests that open additional tabs: the test harness tab itself might get | ||
// unloaded | ||
'browser.tabs.disableBackgroundZombification': false, | ||
// Do not warn when closing all other open tabs | ||
'browser.tabs.warnOnCloseOtherTabs': false, | ||
// Do not warn when multiple tabs will be opened | ||
'browser.tabs.warnOnOpen': false, | ||
// Disable the UI tour. | ||
'browser.uitour.enabled': false, | ||
// Turn off search suggestions in the location bar so as not to trigger | ||
// network connections. | ||
'browser.urlbar.suggest.searches': false, | ||
// Disable first run splash page on Windows 10 | ||
'browser.usedOnWindows10.introURL': '', | ||
// Do not warn on quitting Firefox | ||
'browser.warnOnQuit': false, | ||
// Do not show datareporting policy notifications which can | ||
// interfere with tests | ||
'datareporting.healthreport.about.reportUrl': `http://${server}/dummy/abouthealthreport/`, | ||
'datareporting.healthreport.documentServerURI': `http://${server}/dummy/healthreport/`, | ||
'datareporting.healthreport.logging.consoleEnabled': false, | ||
'datareporting.healthreport.service.enabled': false, | ||
'datareporting.healthreport.service.firstRun': false, | ||
'datareporting.healthreport.uploadEnabled': false, | ||
'datareporting.policy.dataSubmissionEnabled': false, | ||
'datareporting.policy.dataSubmissionPolicyAccepted': false, | ||
'datareporting.policy.dataSubmissionPolicyBypassNotification': true, | ||
// DevTools JSONViewer sometimes fails to load dependencies with its require.js. | ||
// This doesn't affect Puppeteer but spams console (Bug 1424372) | ||
'devtools.jsonview.enabled': false, | ||
// Disable popup-blocker | ||
'dom.disable_open_during_load': false, | ||
// Enable the support for File object creation in the content process | ||
// Required for |Page.setFileInputFiles| protocol method. | ||
'dom.file.createInChild': true, | ||
// Disable the ProcessHangMonitor | ||
'dom.ipc.reportProcessHangs': false, | ||
// Disable slow script dialogues | ||
'dom.max_chrome_script_run_time': 0, | ||
'dom.max_script_run_time': 0, | ||
// Only load extensions from the application and user profile | ||
// AddonManager.SCOPE_PROFILE + AddonManager.SCOPE_APPLICATION | ||
'extensions.autoDisableScopes': 0, | ||
'extensions.enabledScopes': 5, | ||
// Disable metadata caching for installed add-ons by default | ||
'extensions.getAddons.cache.enabled': false, | ||
// Disable installing any distribution extensions or add-ons. | ||
'extensions.installDistroAddons': false, | ||
// Disabled screenshots extension | ||
'extensions.screenshots.disabled': true, | ||
// Turn off extension updates so they do not bother tests | ||
'extensions.update.enabled': false, | ||
// Turn off extension updates so they do not bother tests | ||
'extensions.update.notifyUser': false, | ||
// Make sure opening about:addons will not hit the network | ||
'extensions.webservice.discoverURL': `http://${server}/dummy/discoveryURL`, | ||
// Allow the application to have focus even it runs in the background | ||
'focusmanager.testmode': true, | ||
// Disable useragent updates | ||
'general.useragent.updates.enabled': false, | ||
// Always use network provider for geolocation tests so we bypass the | ||
// macOS dialog raised by the corelocation provider | ||
'geo.provider.testing': true, | ||
// Do not scan Wifi | ||
'geo.wifi.scan': false, | ||
// No hang monitor | ||
'hangmonitor.timeout': 0, | ||
// Show chrome errors and warnings in the error console | ||
'javascript.options.showInConsole': true, | ||
// Disable download and usage of OpenH264: and Widevine plugins | ||
'media.gmp-manager.updateEnabled': false, | ||
// Prevent various error message on the console | ||
// jest-puppeteer asserts that no error message is emitted by the console | ||
'network.cookie.cookieBehavior': 0, | ||
// Do not prompt for temporary redirects | ||
'network.http.prompt-temp-redirect': false, | ||
// Disable speculative connections so they are not reported as leaking | ||
// when they are hanging around | ||
'network.http.speculative-parallel-limit': 0, | ||
// Do not automatically switch between offline and online | ||
'network.manage-offline-status': false, | ||
// Make sure SNTP requests do not hit the network | ||
'network.sntp.pools': server, | ||
// Disable Flash. | ||
'plugin.state.flash': 0, | ||
'privacy.trackingprotection.enabled': false, | ||
// Enable Remote Agent | ||
// https://bugzilla.mozilla.org/show_bug.cgi?id=1544393 | ||
'remote.enabled': true, | ||
// Don't do network connections for mitm priming | ||
'security.certerrors.mitm.priming.enabled': false, | ||
// Local documents have access to all other local documents, | ||
// including directory listings | ||
'security.fileuri.strict_origin_policy': false, | ||
// Do not wait for the notification button security delay | ||
'security.notification_enable_delay': 0, | ||
// Ensure blocklist updates do not hit the network | ||
'services.settings.server': `http://${server}/dummy/blocklist/`, | ||
// Do not automatically fill sign-in forms with known usernames and | ||
// passwords | ||
'signon.autofillForms': false, | ||
// Disable password capture, so that tests that include forms are not | ||
// influenced by the presence of the persistent doorhanger notification | ||
'signon.rememberSignons': false, | ||
// Disable first-run welcome page | ||
'startup.homepage_welcome_url': 'about:blank', | ||
// Disable first-run welcome page | ||
'startup.homepage_welcome_url.additional': '', | ||
// Disable browser animations (tabs, fullscreen, sliding alerts) | ||
'toolkit.cosmeticAnimations.enabled': false, | ||
// We want to collect telemetry, but we don't want to send in the results | ||
'toolkit.telemetry.server': `https://${server}/dummy/telemetry/`, | ||
// Prevent starting into safe mode after application crashes | ||
'toolkit.startup.max_resumed_crashes': -1, | ||
}; | ||
Object.assign(defaultPreferences, extraPrefs); | ||
for (const [key, value] of Object.entries(defaultPreferences)) | ||
userJS.push(`user_pref(${JSON.stringify(key)}, ${JSON.stringify(value)});`); | ||
await writeFileAsync(path.join(profilePath, 'user.js'), userJS.join('\n')); | ||
await writeFileAsync(path.join(profilePath, 'prefs.js'), prefsJS.join('\n')); | ||
return profilePath; | ||
} | ||
} | ||
/** | ||
* @param {!Puppeteer.ChildProcess} chromeProcess | ||
* @param {!Puppeteer.ChildProcess} browserProcess | ||
* @param {number} timeout | ||
@@ -342,5 +744,5 @@ * @param {string} preferredRevision | ||
*/ | ||
function waitForWSEndpoint(chromeProcess, timeout, preferredRevision) { | ||
function waitForWSEndpoint(browserProcess, timeout, preferredRevision) { | ||
return new Promise((resolve, reject) => { | ||
const rl = readline.createInterface({ input: chromeProcess.stderr }); | ||
const rl = readline.createInterface({ input: browserProcess.stderr }); | ||
let stderr = ''; | ||
@@ -350,4 +752,4 @@ const listeners = [ | ||
helper.addEventListener(rl, 'close', () => onClose()), | ||
helper.addEventListener(chromeProcess, 'exit', () => onClose()), | ||
helper.addEventListener(chromeProcess, 'error', error => onClose(error)) | ||
helper.addEventListener(browserProcess, 'exit', () => onClose()), | ||
helper.addEventListener(browserProcess, 'error', error => onClose(error)) | ||
]; | ||
@@ -362,6 +764,6 @@ const timeoutId = timeout ? setTimeout(onTimeout, timeout) : 0; | ||
reject(new Error([ | ||
'Failed to launch chrome!' + (error ? ' ' + error.message : ''), | ||
'Failed to launch the browser process!' + (error ? ' ' + error.message : ''), | ||
stderr, | ||
'', | ||
'TROUBLESHOOTING: https://github.com/GoogleChrome/puppeteer/blob/master/docs/troubleshooting.md', | ||
'TROUBLESHOOTING: https://github.com/puppeteer/puppeteer/blob/master/docs/troubleshooting.md', | ||
'', | ||
@@ -373,3 +775,3 @@ ].join('\n'))); | ||
cleanup(); | ||
reject(new TimeoutError(`Timed out after ${timeout} ms while trying to connect to Chrome! The only Chrome revision guaranteed to work is r${preferredRevision}`)); | ||
reject(new TimeoutError(`Timed out after ${timeout} ms while trying to connect to the browser! Only Chrome at revision r${preferredRevision} is guaranteed to work.`)); | ||
} | ||
@@ -431,2 +833,51 @@ | ||
/** | ||
* @param {ChromeLauncher|FirefoxLauncher} launcher | ||
* | ||
* @return {{executablePath: string, missingText: ?string}} | ||
*/ | ||
function resolveExecutablePath(launcher) { | ||
// puppeteer-core doesn't take into account PUPPETEER_* env variables. | ||
if (!launcher._isPuppeteerCore) { | ||
const executablePath = process.env.PUPPETEER_EXECUTABLE_PATH || process.env.npm_config_puppeteer_executable_path || process.env.npm_package_config_puppeteer_executable_path; | ||
if (executablePath) { | ||
const missingText = !fs.existsSync(executablePath) ? 'Tried to use PUPPETEER_EXECUTABLE_PATH env variable to launch browser but did not find any executable at: ' + executablePath : null; | ||
return { executablePath, missingText }; | ||
} | ||
} | ||
const browserFetcher = new BrowserFetcher(launcher._projectRoot); | ||
if (!launcher._isPuppeteerCore) { | ||
const revision = process.env['PUPPETEER_CHROMIUM_REVISION']; | ||
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 : null; | ||
return {executablePath: revisionInfo.executablePath, missingText}; | ||
} | ||
} | ||
const revisionInfo = browserFetcher.revisionInfo(launcher._preferredRevision); | ||
const missingText = !revisionInfo.local ? `Browser is not downloaded. Run "npm install" or "yarn install"` : null; | ||
return {executablePath: revisionInfo.executablePath, missingText}; | ||
} | ||
/** | ||
* @param {string} projectRoot | ||
* @param {string} preferredRevision | ||
* @param {boolean} isPuppeteerCore | ||
* @param {string=} product | ||
* @return {!Puppeteer.ProductLauncher} | ||
*/ | ||
function Launcher(projectRoot, preferredRevision, isPuppeteerCore, product) { | ||
// puppeteer-core doesn't take into account PUPPETEER_* env variables. | ||
if (!product && !isPuppeteerCore) | ||
product = process.env.PUPPETEER_PRODUCT || process.env.npm_config_puppeteer_product || process.env.npm_package_config_puppeteer_product; | ||
switch (product) { | ||
case 'firefox': | ||
return new FirefoxLauncher(projectRoot, preferredRevision, isPuppeteerCore); | ||
case 'chrome': | ||
default: | ||
return new ChromeLauncher(projectRoot, preferredRevision, isPuppeteerCore); | ||
} | ||
} | ||
/** | ||
* @typedef {Object} Launcher.ChromeArgOptions | ||
@@ -433,0 +884,0 @@ * @property {boolean=} headless |
@@ -18,3 +18,2 @@ /** | ||
const fs = require('fs'); | ||
const path = require('path'); | ||
const EventEmitter = require('events'); | ||
@@ -116,3 +115,2 @@ const mime = require('mime'); | ||
networkManager.on(Events.NetworkManager.RequestFinished, event => this.emit(Events.Page.RequestFinished, event)); | ||
this._fileChooserInterceptionIsDisabled = false; | ||
this._fileChooserInterceptors = new Set(); | ||
@@ -142,5 +140,3 @@ | ||
this._client.send('Log.enable', {}), | ||
this._client.send('Page.setInterceptFileChooserDialog', {enabled: true}).catch(e => { | ||
this._fileChooserInterceptionIsDisabled = true; | ||
}), | ||
this._client.send('Page.setInterceptFileChooserDialog', {enabled: true}), | ||
]); | ||
@@ -152,10 +148,11 @@ } | ||
*/ | ||
_onFileChooser(event) { | ||
if (!this._fileChooserInterceptors.size) { | ||
this._client.send('Page.handleFileChooser', { action: 'fallback' }).catch(debugError); | ||
async _onFileChooser(event) { | ||
if (!this._fileChooserInterceptors.size) | ||
return; | ||
} | ||
const frame = this._frameManager.frame(event.frameId); | ||
const context = await frame.executionContext(); | ||
const element = await context._adoptBackendNodeId(event.backendNodeId); | ||
const interceptors = Array.from(this._fileChooserInterceptors); | ||
this._fileChooserInterceptors.clear(); | ||
const fileChooser = new FileChooser(this._client, event); | ||
const fileChooser = new FileChooser(this._client, element, event); | ||
for (const interceptor of interceptors) | ||
@@ -170,4 +167,2 @@ interceptor.call(null, fileChooser); | ||
async waitForFileChooser(options = {}) { | ||
if (this._fileChooserInterceptionIsDisabled) | ||
throw new Error('File chooser handling does not work with multiple connections to the same page'); | ||
const { | ||
@@ -551,3 +546,3 @@ timeout = this._timeoutSettings.timeout(), | ||
// | ||
// @see https://github.com/GoogleChrome/puppeteer/issues/3865 | ||
// @see https://github.com/puppeteer/puppeteer/issues/3865 | ||
return; | ||
@@ -1360,6 +1355,8 @@ } | ||
* @param {Puppeteer.CDPSession} client | ||
* @param {Puppeteer.ElementHandle} element | ||
* @param {!Protocol.Page.fileChooserOpenedPayload} event | ||
*/ | ||
constructor(client, event) { | ||
constructor(client, element, event) { | ||
this._client = client; | ||
this._element = element; | ||
this._multiple = event.mode !== 'selectSingle'; | ||
@@ -1383,7 +1380,3 @@ this._handled = false; | ||
this._handled = true; | ||
const files = filePaths.map(filePath => path.resolve(filePath)); | ||
await this._client.send('Page.handleFileChooser', { | ||
action: 'accept', | ||
files, | ||
}); | ||
await this._element.uploadFile(...filePaths); | ||
} | ||
@@ -1397,5 +1390,2 @@ | ||
this._handled = true; | ||
await this._client.send('Page.handleFileChooser', { | ||
action: 'cancel', | ||
}); | ||
} | ||
@@ -1402,0 +1392,0 @@ } |
@@ -29,10 +29,13 @@ /** | ||
this._projectRoot = projectRoot; | ||
this._launcher = new Launcher(projectRoot, preferredRevision, isPuppeteerCore); | ||
this._preferredRevision = preferredRevision; | ||
this._isPuppeteerCore = isPuppeteerCore; | ||
} | ||
/** | ||
* @param {!(Launcher.LaunchOptions & Launcher.ChromeArgOptions & Launcher.BrowserOptions)=} options | ||
* @param {!(Launcher.LaunchOptions & Launcher.ChromeArgOptions & Launcher.BrowserOptions & {product?: string, extraPrefsFirefox?: !object})=} options | ||
* @return {!Promise<!Puppeteer.Browser>} | ||
*/ | ||
launch(options) { | ||
if (!this._productName && options) | ||
this._productName = options.product; | ||
return this._launcher.launch(options); | ||
@@ -57,2 +60,19 @@ } | ||
/** | ||
* @return {!Puppeteer.ProductLauncher} | ||
*/ | ||
get _launcher() { | ||
if (!this._lazyLauncher) | ||
this._lazyLauncher = Launcher(this._projectRoot, this._preferredRevision, this._isPuppeteerCore, this._productName); | ||
return this._lazyLauncher; | ||
} | ||
/** | ||
* @return {string} | ||
*/ | ||
get product() { | ||
return this._launcher.product; | ||
} | ||
/** | ||
* @return {Object} | ||
@@ -59,0 +79,0 @@ */ |
{ | ||
"name": "puppeteer-core", | ||
"version": "2.0.0", | ||
"version": "2.1.0", | ||
"description": "A high-level API to control headless Chrome over the DevTools Protocol", | ||
"main": "index.js", | ||
"repository": "github:GoogleChrome/puppeteer", | ||
"repository": "github:puppeteer/puppeteer", | ||
"engines": { | ||
@@ -11,7 +11,8 @@ "node": ">=8.16.0" | ||
"puppeteer": { | ||
"chromium_revision": "706915" | ||
"chromium_revision": "722234" | ||
}, | ||
"scripts": { | ||
"unit": "node test/test.js", | ||
"funit": "BROWSER=firefox node test/test.js", | ||
"fjunit": "PUPPETEER_PRODUCT=juggler node test/test.js", | ||
"funit": "PUPPETEER_PRODUCT=firefox node test/test.js", | ||
"debug-unit": "node --inspect-brk test/test.js", | ||
@@ -33,6 +34,8 @@ "test-doclint": "node utils/doclint/check_public_api/test/test.js && node utils/doclint/preprocessor/test.js", | ||
"dependencies": { | ||
"@types/mime-types": "^2.1.0", | ||
"debug": "^4.1.0", | ||
"extract-zip": "^1.6.6", | ||
"https-proxy-agent": "^3.0.0", | ||
"https-proxy-agent": "^4.0.0", | ||
"mime": "^2.0.3", | ||
"mime-types": "^2.1.25", | ||
"progress": "^2.0.1", | ||
@@ -39,0 +42,0 @@ "proxy-from-env": "^1.0.0", |
# Puppeteer | ||
<!-- [START badges] --> | ||
[![Linux Build Status](https://img.shields.io/travis/com/GoogleChrome/puppeteer/master.svg)](https://travis-ci.com/GoogleChrome/puppeteer) [![Windows Build Status](https://img.shields.io/appveyor/ci/aslushnikov/puppeteer/master.svg?logo=appveyor)](https://ci.appveyor.com/project/aslushnikov/puppeteer/branch/master) [![Build Status](https://api.cirrus-ci.com/github/GoogleChrome/puppeteer.svg)](https://cirrus-ci.com/github/GoogleChrome/puppeteer) [![NPM puppeteer package](https://img.shields.io/npm/v/puppeteer.svg)](https://npmjs.org/package/puppeteer) [![Issue resolution status](https://isitmaintained.com/badge/resolution/GoogleChrome/puppeteer.svg)](https://github.com/GoogleChrome/puppeteer/issues) | ||
[![Linux Build Status](https://img.shields.io/travis/com/puppeteer/puppeteer/master.svg)](https://travis-ci.com/puppeteer/puppeteer) [![Windows Build Status](https://img.shields.io/appveyor/ci/mathiasbynens/puppeteer/master.svg?logo=appveyor)](https://ci.appveyor.com/project/mathiasbynens/puppeteer/branch/master) [![Build Status](https://api.cirrus-ci.com/github/puppeteer/puppeteer.svg)](https://cirrus-ci.com/github/puppeteer/puppeteer) [![npm puppeteer package](https://img.shields.io/npm/v/puppeteer.svg)](https://npmjs.org/package/puppeteer) [![Issue resolution status](https://isitmaintained.com/badge/resolution/puppeteer/puppeteer.svg)](https://github.com/puppeteer/puppeteer/issues) | ||
<!-- [END badges] --> | ||
@@ -9,3 +9,3 @@ | ||
###### [API](https://github.com/GoogleChrome/puppeteer/blob/v2.0.0/docs/api.md) | [FAQ](#faq) | [Contributing](https://github.com/GoogleChrome/puppeteer/blob/master/CONTRIBUTING.md) | [Troubleshooting](https://github.com/GoogleChrome/puppeteer/blob/master/docs/troubleshooting.md) | ||
###### [API](https://github.com/puppeteer/puppeteer/blob/v2.1.0/docs/api.md) | [FAQ](#faq) | [Contributing](https://github.com/puppeteer/puppeteer/blob/master/CONTRIBUTING.md) | [Troubleshooting](https://github.com/puppeteer/puppeteer/blob/master/docs/troubleshooting.md) | ||
@@ -41,3 +41,3 @@ > Puppeteer is a Node library which provides a high-level API to control Chrome or Chromium over the [DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/). Puppeteer runs [headless](https://developers.google.com/web/updates/2017/04/headless-chrome) by default, but can be configured to run full (non-headless) Chrome or Chromium. | ||
Note: When you install Puppeteer, it downloads a recent version of Chromium (~170MB Mac, ~282MB Linux, ~280MB Win) that is guaranteed to work with the API. To skip the download, see [Environment variables](https://github.com/GoogleChrome/puppeteer/blob/v2.0.0/docs/api.md#environment-variables). | ||
Note: When you install Puppeteer, it downloads a recent version of Chromium (~170MB Mac, ~282MB Linux, ~280MB Win) that is guaranteed to work with the API. To skip the download, see [Environment variables](https://github.com/puppeteer/puppeteer/blob/v2.1.0/docs/api.md#environment-variables). | ||
@@ -58,3 +58,3 @@ | ||
See [puppeteer vs puppeteer-core](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#puppeteer-vs-puppeteer-core). | ||
See [puppeteer vs puppeteer-core](https://github.com/puppeteer/puppeteer/blob/master/docs/api.md#puppeteer-vs-puppeteer-core). | ||
@@ -69,3 +69,3 @@ ### Usage | ||
Puppeteer will be familiar to people using other browser testing frameworks. You create an instance | ||
of `Browser`, open pages, and then manipulate them with [Puppeteer's API](https://github.com/GoogleChrome/puppeteer/blob/v2.0.0/docs/api.md#). | ||
of `Browser`, open pages, and then manipulate them with [Puppeteer's API](https://github.com/puppeteer/puppeteer/blob/v2.1.0/docs/api.md#). | ||
@@ -95,3 +95,3 @@ **Example** - navigating to https://example.com and saving a screenshot as *example.png*: | ||
Puppeteer sets an initial page size to 800×600px, which defines the screenshot size. The page size can be customized with [`Page.setViewport()`](https://github.com/GoogleChrome/puppeteer/blob/v2.0.0/docs/api.md#pagesetviewportviewport). | ||
Puppeteer sets an initial page size to 800×600px, which defines the screenshot size. The page size can be customized with [`Page.setViewport()`](https://github.com/puppeteer/puppeteer/blob/v2.1.0/docs/api.md#pagesetviewportviewport). | ||
@@ -121,3 +121,3 @@ **Example** - create a PDF. | ||
See [`Page.pdf()`](https://github.com/GoogleChrome/puppeteer/blob/v2.0.0/docs/api.md#pagepdfoptions) for more information about creating pdfs. | ||
See [`Page.pdf()`](https://github.com/puppeteer/puppeteer/blob/v2.1.0/docs/api.md#pagepdfoptions) for more information about creating pdfs. | ||
@@ -157,3 +157,3 @@ **Example** - evaluate script in the context of the page | ||
See [`Page.evaluate()`](https://github.com/GoogleChrome/puppeteer/blob/v2.0.0/docs/api.md#pageevaluatepagefunction-args) for more information on `evaluate` and related methods like `evaluateOnNewDocument` and `exposeFunction`. | ||
See [`Page.evaluate()`](https://github.com/puppeteer/puppeteer/blob/v2.1.0/docs/api.md#pageevaluatepagefunction-args) for more information on `evaluate` and related methods like `evaluateOnNewDocument` and `exposeFunction`. | ||
@@ -167,3 +167,3 @@ <!-- [END getstarted] --> | ||
Puppeteer launches Chromium in [headless mode](https://developers.google.com/web/updates/2017/04/headless-chrome). To launch a full version of Chromium, set the [`headless` option](https://github.com/GoogleChrome/puppeteer/blob/v2.0.0/docs/api.md#puppeteerlaunchoptions) when launching a browser: | ||
Puppeteer launches Chromium in [headless mode](https://developers.google.com/web/updates/2017/04/headless-chrome). To launch a full version of Chromium, set the [`headless` option](https://github.com/puppeteer/puppeteer/blob/v2.1.0/docs/api.md#puppeteerlaunchoptions) when launching a browser: | ||
@@ -184,3 +184,3 @@ ```js | ||
See [`Puppeteer.launch()`](https://github.com/GoogleChrome/puppeteer/blob/v2.0.0/docs/api.md#puppeteerlaunchoptions) for more information. | ||
See [`Puppeteer.launch()`](https://github.com/puppeteer/puppeteer/blob/v2.1.0/docs/api.md#puppeteerlaunchoptions) for more information. | ||
@@ -197,4 +197,4 @@ 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/+/master/docs/chromium_browser_vs_google_chrome.md) describes some differences for Linux users. | ||
- [API Documentation](https://github.com/GoogleChrome/puppeteer/blob/v2.0.0/docs/api.md) | ||
- [Examples](https://github.com/GoogleChrome/puppeteer/tree/master/examples/) | ||
- [API Documentation](https://github.com/puppeteer/puppeteer/blob/v2.1.0/docs/api.md) | ||
- [Examples](https://github.com/puppeteer/puppeteer/tree/master/examples/) | ||
- [Community list of Puppeteer resources](https://github.com/transitive-bullshit/awesome-puppeteer) | ||
@@ -298,3 +298,3 @@ | ||
Check out [contributing guide](https://github.com/GoogleChrome/puppeteer/blob/master/CONTRIBUTING.md) to get an overview of Puppeteer development. | ||
Check out [contributing guide](https://github.com/puppeteer/puppeteer/blob/master/CONTRIBUTING.md) to get an overview of Puppeteer development. | ||
@@ -308,3 +308,3 @@ <!-- [START faq] --> | ||
The Chrome DevTools team maintains the library, but we'd love your help and expertise on the project! | ||
See [Contributing](https://github.com/GoogleChrome/puppeteer/blob/master/CONTRIBUTING.md). | ||
See [Contributing](https://github.com/puppeteer/puppeteer/blob/master/CONTRIBUTING.md). | ||
@@ -335,3 +335,3 @@ #### Q: What are Puppeteer’s goals and principles? | ||
- Puppeteer requires zero setup and comes bundled with the Chromium version it works best with, making it [very easy to start with](https://github.com/GoogleChrome/puppeteer/#getting-started). At the end of the day, it’s better to have a few tests running chromium-only, than no tests at all. | ||
- Puppeteer requires zero setup and comes bundled with the Chromium version it works best with, making it [very easy to start with](https://github.com/puppeteer/puppeteer/#getting-started). At the end of the day, it’s better to have a few tests running chromium-only, than no tests at all. | ||
- Puppeteer has event-driven architecture, which removes a lot of potential flakiness. There’s no need for evil “sleep(1000)” calls in puppeteer scripts. | ||
@@ -346,5 +346,5 @@ - Puppeteer runs headless by default, which makes it fast to run. Puppeteer v1.5.0 also exposes browser contexts, making it possible to efficiently parallelize test execution. | ||
This is not an artificial constraint: A lot of work on Puppeteer is actually taking place in the Chromium repository. Here’s a typical story: | ||
- A Puppeteer bug is reported: https://github.com/GoogleChrome/puppeteer/issues/2709 | ||
- A Puppeteer bug is reported: https://github.com/puppeteer/puppeteer/issues/2709 | ||
- It turned out this is an issue with the DevTools protocol, so we’re fixing it in Chromium: https://chromium-review.googlesource.com/c/chromium/src/+/1102154 | ||
- Once the upstream fix is landed, we roll updated Chromium into Puppeteer: https://github.com/GoogleChrome/puppeteer/pull/2769 | ||
- Once the upstream fix is landed, we roll updated Chromium into Puppeteer: https://github.com/puppeteer/puppeteer/pull/2769 | ||
@@ -360,3 +360,3 @@ However, oftentimes it is desirable to use Puppeteer with the official Google Chrome rather than Chromium. For this to work, you should install a `puppeteer-core` version that corresponds to the Chrome version. | ||
Look for `chromium_revision` in [package.json](https://github.com/GoogleChrome/puppeteer/blob/master/package.json). To find the corresponding Chromium commit and version number, search for the revision prefixed by an `r` in [OmahaProxy](https://omahaproxy.appspot.com/)'s "Find Releases" section. | ||
Look for `chromium_revision` in [package.json](https://github.com/puppeteer/puppeteer/blob/master/package.json). To find the corresponding Chromium commit and version number, search for the revision prefixed by an `r` in [OmahaProxy](https://omahaproxy.appspot.com/)'s "Find Releases" section. | ||
@@ -391,9 +391,9 @@ #### Q: What’s considered a “Navigation”? | ||
You may find that Puppeteer does not behave as expected when controlling pages that incorporate audio and video. (For example, [video playback/screenshots is likely to fail](https://github.com/GoogleChrome/puppeteer/issues/291).) There are two reasons for this: | ||
You may find that Puppeteer does not behave as expected when controlling pages that incorporate audio and video. (For example, [video playback/screenshots is likely to fail](https://github.com/puppeteer/puppeteer/issues/291).) There are two reasons for this: | ||
* Puppeteer is bundled with Chromium — not Chrome — and so by default, it inherits all of [Chromium's media-related limitations](https://www.chromium.org/audio-video). This means that Puppeteer does not support licensed formats such as AAC or H.264. (However, it is possible to force Puppeteer to use a separately-installed version Chrome instead of Chromium via the [`executablePath` option to `puppeteer.launch`](https://github.com/GoogleChrome/puppeteer/blob/v2.0.0/docs/api.md#puppeteerlaunchoptions). You should only use this configuration if you need an official release of Chrome that supports these media formats.) | ||
* Puppeteer is bundled with Chromium — not Chrome — and so by default, it inherits all of [Chromium's media-related limitations](https://www.chromium.org/audio-video). This means that Puppeteer does not support licensed formats such as AAC or H.264. (However, it is possible to force Puppeteer to use a separately-installed version Chrome instead of Chromium via the [`executablePath` option to `puppeteer.launch`](https://github.com/puppeteer/puppeteer/blob/v2.1.0/docs/api.md#puppeteerlaunchoptions). You should only use this configuration if you need an official release of Chrome that supports these media formats.) | ||
* Since Puppeteer (in all configurations) controls a desktop version of Chromium/Chrome, features that are only supported by the mobile version of Chrome are not supported. This means that Puppeteer [does not support HTTP Live Streaming (HLS)](https://caniuse.com/#feat=http-live-streaming). | ||
#### Q: I am having trouble installing / running Puppeteer in my test environment. Where should I look for help? | ||
We have a [troubleshooting](https://github.com/GoogleChrome/puppeteer/blob/master/docs/troubleshooting.md) guide for various operating systems that lists the required dependencies. | ||
We have a [troubleshooting](https://github.com/puppeteer/puppeteer/blob/master/docs/troubleshooting.md) guide for various operating systems that lists the required dependencies. | ||
@@ -413,3 +413,3 @@ #### Q: How do I try/test a prerelease version of Puppeteer? | ||
There are many ways to get help on Puppeteer: | ||
- [bugtracker](https://github.com/GoogleChrome/puppeteer/issues) | ||
- [bugtracker](https://github.com/puppeteer/puppeteer/issues) | ||
- [Stack Overflow](https://stackoverflow.com/questions/tagged/puppeteer) | ||
@@ -416,0 +416,0 @@ - [slack channel](https://join.slack.com/t/puppeteer/shared_invite/enQtMzU4MjIyMDA5NTM4LWI0YTE0MjM0NWQzYmE2MTRmNjM1ZTBkN2MxNmJmNTIwNTJjMmFhOWFjMGExMDViYjk2YjU2ZmYzMmE1NmExYzc) |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 3 instances in 1 package
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
887752
40
24358
10
40
11
+ Added@types/mime-types@^2.1.0
+ Addedmime-types@^2.1.25
+ Added@types/mime-types@2.1.4(transitive)
+ Addedagent-base@5.1.1(transitive)
+ Addedhttps-proxy-agent@4.0.0(transitive)
+ Addedmime-db@1.52.0(transitive)
+ Addedmime-types@2.1.35(transitive)
- Removedagent-base@4.3.0(transitive)
- Removeddebug@3.2.7(transitive)
- Removedes6-promise@4.2.8(transitive)
- Removedes6-promisify@5.0.0(transitive)
- Removedhttps-proxy-agent@3.0.1(transitive)
Updatedhttps-proxy-agent@^4.0.0