puppeteer-core
Advanced tools
Comparing version 1.19.0 to 1.20.0
@@ -15,4 +15,4 @@ <!-- gen:toc --> | ||
- [For Project Maintainers](#for-project-maintainers) | ||
* [Releasing to NPM](#releasing-to-npm) | ||
* [Updating NPM dist tags](#updating-npm-dist-tags) | ||
* [Releasing to npm](#releasing-to-npm) | ||
* [Updating npm dist tags](#updating-npm-dist-tags) | ||
<!-- gen:stop --> | ||
@@ -80,2 +80,3 @@ | ||
When authoring new API methods, consider the following: | ||
- Expose as little information as needed. When in doubt, don’t expose new information. | ||
@@ -128,5 +129,5 @@ - Methods are used in favor of getters/setters. | ||
All public API should have a descriptive entry in the [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/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. | ||
To run documentation linter, use: | ||
To run the documentation linter, use: | ||
@@ -153,3 +154,3 @@ ```bash | ||
Puppeteer tests are located in [test/test.js](https://github.com/GoogleChrome/puppeteer/blob/master/test/test.js) | ||
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. | ||
@@ -185,3 +186,3 @@ Despite being named 'unit', these are integration tests, making sure public API methods and events work as expected. | ||
expect(response.ok).toBe(true); | ||
}) | ||
}); | ||
``` | ||
@@ -197,3 +198,3 @@ | ||
expect(response.ok).toBe(true); | ||
}) | ||
}); | ||
``` | ||
@@ -227,3 +228,3 @@ | ||
Every public API method or event should be called at least once in tests. To ensure this, there's a coverage command which tracks calls to public API and reports back if some methods/events were not called. | ||
Every public API method or event should be called at least once in tests. To ensure this, there's a `coverage` command which tracks calls to public API and reports back if some methods/events were not called. | ||
@@ -242,7 +243,8 @@ Run coverage: | ||
## Releasing to NPM | ||
## Releasing to npm | ||
Releasing to NPM consists of 3 phases: | ||
Releasing to npm consists of the following phases: | ||
1. Source Code: mark a release. | ||
1. Bump `package.json` version following the SEMVER rules and send a PR titled `'chore: mark version vXXX.YYY.ZZZ'` ([example](https://github.com/GoogleChrome/puppeteer/commit/808bf8e5582482a1d849ff22a51e52024810905c)). | ||
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**. | ||
@@ -252,12 +254,12 @@ - **WHY**: there are linters in place that help to avoid unnecessary errors, e.g. [like this](https://github.com/GoogleChrome/puppeteer/pull/2446) | ||
4. Once merged, publish release notes using the "create new tag" option. | ||
- **NOTE**: tag names are prefixed with `'v'`, e.g. for version `1.4.0` tag is `v1.4.0`. | ||
2. Publish `puppeteer` to NPM. | ||
- **NOTE**: tag names are prefixed with `'v'`, e.g. for version `1.4.0` the tag is `v1.4.0`. | ||
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. | ||
2. Run `git status` and make sure there are no untracked files. | ||
- **WHY**: this is to avoid bundling unnecessary files to NPM package | ||
- **WHY**: this is to avoid adding unnecessary files to the npm package. | ||
3. Run [`pkgfiles`](https://www.npmjs.com/package/pkgfiles) to make sure you don't publish anything unnecessary. | ||
4. Run `npm publish`. This will publish `puppeteer` package. | ||
3. Publish `puppeteer-core` to NPM. | ||
1. Run `./utils/prepare_puppeteer_core.js`. The script will change the name inside `package.json` to `puppeteer-core`. | ||
2. Run `npm publish`. This will publish `puppeteer-core` package. | ||
4. Run `npm publish`. This publishes the `puppeteer` package. | ||
3. Publish `puppeteer-core` to npm. | ||
1. Run `./utils/prepare_puppeteer_core.js`. The script changes the name inside `package.json` to `puppeteer-core`. | ||
2. Run `npm publish`. This publishes the `puppeteer-core` package. | ||
3. Run `git reset --hard` to reset the changes to `package.json`. | ||
@@ -269,8 +271,9 @@ 4. Source Code: mark post-release. | ||
## Updating NPM dist tags | ||
## Updating npm dist tags | ||
For both `puppeteer` and `puppeteer-firefox` we maintain the following NPM Tags: | ||
- `chrome-*` tags, e.g. `chrome-75` and so on. These tags match Puppeteer version that corresponds to the `chrome-*` release. | ||
- `chrome-stable` tag. This tag points to the Puppeteer version that works with current Chrome stable. | ||
For both `puppeteer` and `puppeteer-core` we maintain the following npm tags: | ||
- `chrome-*` tags, e.g. `chrome-75` and so on. These tags match the Puppeteer version that corresponds to the `chrome-*` release. | ||
- `chrome-stable` tag. This tag points to the Puppeteer version that works with the current Chrome stable release. | ||
These tags are updated on every Puppeteer release. | ||
@@ -280,3 +283,3 @@ | ||
Manging tags 101: | ||
Managing tags 101: | ||
@@ -283,0 +286,0 @@ ```bash |
@@ -475,2 +475,26 @@ /** | ||
{ | ||
'name': 'iPhone XR', | ||
'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1', | ||
'viewport': { | ||
'width': 414, | ||
'height': 896, | ||
'deviceScaleFactor': 3, | ||
'isMobile': true, | ||
'hasTouch': true, | ||
'isLandscape': false | ||
} | ||
}, | ||
{ | ||
'name': 'iPhone XR landscape', | ||
'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1', | ||
'viewport': { | ||
'width': 896, | ||
'height': 414, | ||
'deviceScaleFactor': 3, | ||
'isMobile': true, | ||
'hasTouch': true, | ||
'isLandscape': true | ||
} | ||
}, | ||
{ | ||
'name': 'JioPhone 2', | ||
@@ -477,0 +501,0 @@ 'userAgent': 'Mozilla/5.0 (Mobile; LYF/F300B/LYF-F300B-001-01-15-130718-i;Android; rv:48.0) Gecko/48.0 Firefox/48.0 KAIOS/2.5', |
@@ -392,24 +392,12 @@ /** | ||
/** | ||
* @param {string} selector | ||
* @param {!Array<string>} values | ||
* @return {!Promise<!Array<string>>} | ||
*/ | ||
select(selector, ...values){ | ||
for (const value of values) | ||
assert(helper.isString(value), 'Values must be strings. Found value "' + value + '" of type "' + (typeof value) + '"'); | ||
return this.$eval(selector, (element, values) => { | ||
if (element.nodeName.toLowerCase() !== 'select') | ||
throw new Error('Element is not a <select> element.'); | ||
const options = Array.from(element.options); | ||
element.value = undefined; | ||
for (const option of options) { | ||
option.selected = values.includes(option.value); | ||
if (option.selected && !element.multiple) | ||
break; | ||
} | ||
element.dispatchEvent(new Event('input', { 'bubbles': true })); | ||
element.dispatchEvent(new Event('change', { 'bubbles': true })); | ||
return options.filter(option => option.selected).map(option => option.value); | ||
}, values); | ||
* @param {string} selector | ||
* @param {!Array<string>} values | ||
* @return {!Promise<!Array<string>>} | ||
*/ | ||
async select(selector, ...values) { | ||
const handle = await this.$(selector); | ||
assert(handle, 'No node found for selector: ' + selector); | ||
const result = await handle.select(...values); | ||
await handle.dispose(); | ||
return result; | ||
} | ||
@@ -416,0 +404,0 @@ |
@@ -38,4 +38,3 @@ /** | ||
this._page = page; | ||
this._networkManager = new NetworkManager(client, ignoreHTTPSErrors); | ||
this._networkManager.setFrameManager(this); | ||
this._networkManager = new NetworkManager(client, ignoreHTTPSErrors, this); | ||
this._timeoutSettings = timeoutSettings; | ||
@@ -42,0 +41,0 @@ /** @type {!Map<string, !Frame>} */ |
@@ -179,5 +179,7 @@ /** | ||
* @param {function} predicate | ||
* @param {number} timeout | ||
* @param {!Promise<!Error>} abortPromise | ||
* @return {!Promise} | ||
*/ | ||
static waitForEvent(emitter, eventName, predicate, timeout) { | ||
static async waitForEvent(emitter, eventName, predicate, timeout, abortPromise) { | ||
let eventTimeout, resolveCallback, rejectCallback; | ||
@@ -191,3 +193,2 @@ const promise = new Promise((resolve, reject) => { | ||
return; | ||
cleanup(); | ||
resolveCallback(event); | ||
@@ -197,3 +198,2 @@ }); | ||
eventTimeout = setTimeout(() => { | ||
cleanup(); | ||
rejectCallback(new TimeoutError('Timeout exceeded while waiting for event')); | ||
@@ -206,3 +206,12 @@ }, timeout); | ||
} | ||
return promise; | ||
const result = await Promise.race([promise, abortPromise]).then(r => { | ||
cleanup(); | ||
return r; | ||
}, e => { | ||
cleanup(); | ||
throw e; | ||
}); | ||
if (result instanceof Error) | ||
throw result; | ||
return result; | ||
} | ||
@@ -209,0 +218,0 @@ |
@@ -50,2 +50,20 @@ /** | ||
/** | ||
* @param {Function|String} pageFunction | ||
* @param {!Array<*>} args | ||
* @return {!Promise<(!Object|undefined)>} | ||
*/ | ||
async evaluate(pageFunction, ...args) { | ||
return await this.executionContext().evaluate(pageFunction, this, ...args); | ||
} | ||
/** | ||
* @param {Function|string} pageFunction | ||
* @param {!Array<*>} args | ||
* @return {!Promise<!Puppeteer.JSHandle>} | ||
*/ | ||
async evaluateHandle(pageFunction, ...args) { | ||
return await this.executionContext().evaluateHandle(pageFunction, this, ...args); | ||
} | ||
/** | ||
* @param {string} propertyName | ||
@@ -55,7 +73,7 @@ * @return {!Promise<?JSHandle>} | ||
async getProperty(propertyName) { | ||
const objectHandle = await this._context.evaluateHandle((object, propertyName) => { | ||
const objectHandle = await this.evaluateHandle((object, propertyName) => { | ||
const result = {__proto__: null}; | ||
result[propertyName] = object[propertyName]; | ||
return result; | ||
}, this, propertyName); | ||
}, propertyName); | ||
const properties = await objectHandle.getProperties(); | ||
@@ -165,3 +183,3 @@ const result = properties.get(propertyName) || null; | ||
async _scrollIntoViewIfNeeded() { | ||
const error = await this.executionContext().evaluate(async(element, pageJavascriptEnabled) => { | ||
const error = await this.evaluate(async(element, pageJavascriptEnabled) => { | ||
if (!element.isConnected) | ||
@@ -186,3 +204,3 @@ return 'Node is detached from document'; | ||
return false; | ||
}, this, this._page._javascriptEnabled); | ||
}, this._page._javascriptEnabled); | ||
if (error) | ||
@@ -274,2 +292,26 @@ throw new Error(error); | ||
/** | ||
* @param {!Array<string>} values | ||
* @return {!Promise<!Array<string>>} | ||
*/ | ||
async select(...values) { | ||
for (const value of values) | ||
assert(helper.isString(value), 'Values must be strings. Found value "' + value + '" of type "' + (typeof value) + '"'); | ||
return this.evaluate((element, values) => { | ||
if (element.nodeName.toLowerCase() !== 'select') | ||
throw new Error('Element is not a <select> element.'); | ||
const options = Array.from(element.options); | ||
element.value = undefined; | ||
for (const option of options) { | ||
option.selected = values.includes(option.value); | ||
if (option.selected && !element.multiple) | ||
break; | ||
} | ||
element.dispatchEvent(new Event('input', { 'bubbles': true })); | ||
element.dispatchEvent(new Event('change', { 'bubbles': true })); | ||
return options.filter(option => option.selected).map(option => option.value); | ||
}, values); | ||
} | ||
/** | ||
* @param {!Array<string>} filePaths | ||
@@ -290,3 +332,3 @@ */ | ||
async focus() { | ||
await this.executionContext().evaluate(element => element.focus(), this); | ||
await this.evaluate(element => element.focus()); | ||
} | ||
@@ -401,5 +443,5 @@ | ||
async $(selector) { | ||
const handle = await this.executionContext().evaluateHandle( | ||
const handle = await this.evaluateHandle( | ||
(element, selector) => element.querySelector(selector), | ||
this, selector | ||
selector | ||
); | ||
@@ -418,5 +460,5 @@ const element = handle.asElement(); | ||
async $$(selector) { | ||
const arrayHandle = await this.executionContext().evaluateHandle( | ||
const arrayHandle = await this.evaluateHandle( | ||
(element, selector) => element.querySelectorAll(selector), | ||
this, selector | ||
selector | ||
); | ||
@@ -444,3 +486,3 @@ const properties = await arrayHandle.getProperties(); | ||
throw new Error(`Error: failed to find element matching selector "${selector}"`); | ||
const result = await this.executionContext().evaluate(pageFunction, elementHandle, ...args); | ||
const result = await elementHandle.evaluate(pageFunction, ...args); | ||
await elementHandle.dispose(); | ||
@@ -457,8 +499,8 @@ return result; | ||
async $$eval(selector, pageFunction, ...args) { | ||
const arrayHandle = await this.executionContext().evaluateHandle( | ||
const arrayHandle = await this.evaluateHandle( | ||
(element, selector) => Array.from(element.querySelectorAll(selector)), | ||
this, selector | ||
selector | ||
); | ||
const result = await this.executionContext().evaluate(pageFunction, arrayHandle, ...args); | ||
const result = await arrayHandle.evaluate(pageFunction, ...args); | ||
await arrayHandle.dispose(); | ||
@@ -473,3 +515,3 @@ return result; | ||
async $x(expression) { | ||
const arrayHandle = await this.executionContext().evaluateHandle( | ||
const arrayHandle = await this.evaluateHandle( | ||
(element, expression) => { | ||
@@ -484,3 +526,3 @@ const document = element.ownerDocument || element; | ||
}, | ||
this, expression | ||
expression | ||
); | ||
@@ -502,3 +544,3 @@ const properties = await arrayHandle.getProperties(); | ||
isIntersectingViewport() { | ||
return this.executionContext().evaluate(async element => { | ||
return this.evaluate(async element => { | ||
const visibleRatio = await new Promise(resolve => { | ||
@@ -512,3 +554,3 @@ const observer = new IntersectionObserver(entries => { | ||
return visibleRatio > 0; | ||
}, this); | ||
}); | ||
} | ||
@@ -515,0 +557,0 @@ } |
@@ -49,5 +49,4 @@ /** | ||
'--disable-extensions', | ||
// TODO: Support OOOPIF. @see https://github.com/GoogleChrome/puppeteer/issues/2548 | ||
// BlinkGenPropertyTrees disabled due to crbug.com/937609 | ||
'--disable-features=site-per-process,TranslateUI,BlinkGenPropertyTrees', | ||
'--disable-features=TranslateUI,BlinkGenPropertyTrees', | ||
'--disable-hang-monitor', | ||
@@ -54,0 +53,0 @@ '--disable-ipc-flooding-protection', |
@@ -23,8 +23,9 @@ /** | ||
* @param {!Puppeteer.CDPSession} client | ||
* @param {!Puppeteer.FrameManager} frameManager | ||
*/ | ||
constructor(client, ignoreHTTPSErrors) { | ||
constructor(client, ignoreHTTPSErrors, frameManager) { | ||
super(); | ||
this._client = client; | ||
this._ignoreHTTPSErrors = ignoreHTTPSErrors; | ||
this._frameManager = null; | ||
this._frameManager = frameManager; | ||
/** @type {!Map<string, !Request>} */ | ||
@@ -65,9 +66,2 @@ this._requestIdToRequest = new Map(); | ||
/** | ||
* @param {!Puppeteer.FrameManager} frameManager | ||
*/ | ||
setFrameManager(frameManager) { | ||
this._frameManager = frameManager; | ||
} | ||
/** | ||
* @param {?{username: string, password: string}} credentials | ||
@@ -239,3 +233,3 @@ */ | ||
} | ||
const frame = event.frameId && this._frameManager ? this._frameManager.frame(event.frameId) : null; | ||
const frame = event.frameId ? this._frameManager.frame(event.frameId) : null; | ||
const request = new Request(this._client, frame, interceptionId, this._userRequestInterceptionEnabled, event, redirectChain); | ||
@@ -729,4 +723,6 @@ this._requestIdToRequest.set(event.requestId, request); | ||
const result = []; | ||
for (const name in headers) | ||
result.push({name, value: headers[name] + ''}); | ||
for (const name in headers) { | ||
if (!Object.is(headers[name], undefined)) | ||
result.push({name, value: headers[name] + ''}); | ||
} | ||
return result; | ||
@@ -733,0 +729,0 @@ } |
@@ -697,2 +697,8 @@ /** | ||
_sessionClosePromise() { | ||
if (!this._disconnectPromise) | ||
this._disconnectPromise = new Promise(fulfill => this._client.once(Events.CDPSession.Disconnected, () => fulfill(new Error('Target closed')))); | ||
return this._disconnectPromise; | ||
} | ||
/** | ||
@@ -713,3 +719,3 @@ * @param {(string|Function)} urlOrPredicate | ||
return false; | ||
}, timeout); | ||
}, timeout, this._sessionClosePromise()); | ||
} | ||
@@ -732,3 +738,3 @@ | ||
return false; | ||
}, timeout); | ||
}, timeout, this._sessionClosePromise()); | ||
} | ||
@@ -885,3 +891,3 @@ | ||
assert(options.clip.width !== 0, 'Expected options.clip.width not to be 0.'); | ||
assert(options.clip.height !== 0, 'Expected options.clip.width not to be 0.'); | ||
assert(options.clip.height !== 0, 'Expected options.clip.height not to be 0.'); | ||
} | ||
@@ -888,0 +894,0 @@ return this._screenshotTaskQueue.postTask(this._screenshotTask.bind(this, screenshotType, options)); |
@@ -20,3 +20,2 @@ /** | ||
const {Worker} = require('./Worker'); | ||
const {Connection} = require('./Connection'); | ||
@@ -88,12 +87,5 @@ class Target { | ||
if (!this._workerPromise) { | ||
this._workerPromise = this._sessionFactory().then(async client => { | ||
// Top level workers have a fake page wrapping the actual worker. | ||
const [targetAttached] = await Promise.all([ | ||
new Promise(x => client.once('Target.attachedToTarget', x)), | ||
client.send('Target.setAutoAttach', {autoAttach: true, waitForDebuggerOnStart: false, flatten: true}), | ||
]); | ||
const session = Connection.fromSession(client).session(targetAttached.sessionId); | ||
// TODO(einbinder): Make workers send their console logs. | ||
return new Worker(session, this._targetInfo.url, () => {} /* consoleAPICalled */, () => {} /* exceptionThrown */); | ||
}); | ||
// TODO(einbinder): Make workers send their console logs. | ||
this._workerPromise = this._sessionFactory() | ||
.then(client => new Worker(client, this._targetInfo.url, () => {} /* consoleAPICalled */, () => {} /* exceptionThrown */)); | ||
} | ||
@@ -100,0 +92,0 @@ return this._workerPromise; |
@@ -475,2 +475,26 @@ /** | ||
{ | ||
'name': 'iPhone XR', | ||
'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1', | ||
'viewport': { | ||
'width': 414, | ||
'height': 896, | ||
'deviceScaleFactor': 3, | ||
'isMobile': true, | ||
'hasTouch': true, | ||
'isLandscape': false | ||
} | ||
}, | ||
{ | ||
'name': 'iPhone XR landscape', | ||
'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1', | ||
'viewport': { | ||
'width': 896, | ||
'height': 414, | ||
'deviceScaleFactor': 3, | ||
'isMobile': true, | ||
'hasTouch': true, | ||
'isLandscape': true | ||
} | ||
}, | ||
{ | ||
'name': 'JioPhone 2', | ||
@@ -477,0 +501,0 @@ 'userAgent': 'Mozilla/5.0 (Mobile; LYF/F300B/LYF-F300B-001-01-15-130718-i;Android; rv:48.0) Gecko/48.0 Firefox/48.0 KAIOS/2.5', |
@@ -886,25 +886,39 @@ /** | ||
/** | ||
* @param {string} selector | ||
* @param {!Array<string>} values | ||
* @return {!Promise<!Array<string>>} | ||
*/ | ||
select(selector, ...values){ | ||
for (const value of values) | ||
assert(helper.isString(value), 'Values must be strings. Found value "' + value + '" of type "' + (typeof value) + '"'); | ||
return this.$eval(selector, (element, values) => { | ||
if (element.nodeName.toLowerCase() !== 'select') | ||
throw new Error('Element is not a <select> element.'); | ||
const options = Array.from(element.options); | ||
element.value = undefined; | ||
for (const option of options) { | ||
option.selected = values.includes(option.value); | ||
if (option.selected && !element.multiple) | ||
break; | ||
* @param {string} selector | ||
* @param {!Array<string>} values | ||
* @return {!Promise<!Array<string>>} | ||
*/ | ||
/* async */ select(selector, ...values) {return (fn => { | ||
const gen = fn.call(this); | ||
return new Promise((resolve, reject) => { | ||
function step(key, arg) { | ||
let info, value; | ||
try { | ||
info = gen[key](arg); | ||
value = info.value; | ||
} catch (error) { | ||
reject(error); | ||
return; | ||
} | ||
element.dispatchEvent(new Event('input', { 'bubbles': true })); | ||
element.dispatchEvent(new Event('change', { 'bubbles': true })); | ||
return options.filter(option => option.selected).map(option => option.value); | ||
}, values); | ||
} | ||
if (info.done) { | ||
resolve(value); | ||
} else { | ||
return Promise.resolve(value).then( | ||
value => { | ||
step('next', value); | ||
}, | ||
err => { | ||
step('throw', err); | ||
}); | ||
} | ||
} | ||
return step('next'); | ||
}); | ||
})(function*(){ | ||
const handle = (yield this.$(selector)); | ||
assert(handle, 'No node found for selector: ' + selector); | ||
const result = (yield handle.select(...values)); | ||
(yield handle.dispose()); | ||
return result; | ||
});} | ||
@@ -911,0 +925,0 @@ /** |
@@ -38,4 +38,3 @@ /** | ||
this._page = page; | ||
this._networkManager = new NetworkManager(client, ignoreHTTPSErrors); | ||
this._networkManager.setFrameManager(this); | ||
this._networkManager = new NetworkManager(client, ignoreHTTPSErrors, this); | ||
this._timeoutSettings = timeoutSettings; | ||
@@ -42,0 +41,0 @@ /** @type {!Map<string, !Frame>} */ |
@@ -205,5 +205,33 @@ /** | ||
* @param {function} predicate | ||
* @param {number} timeout | ||
* @param {!Promise<!Error>} abortPromise | ||
* @return {!Promise} | ||
*/ | ||
static waitForEvent(emitter, eventName, predicate, timeout) { | ||
static /* async */ waitForEvent(emitter, eventName, predicate, timeout, abortPromise) {return (fn => { | ||
const gen = fn.call(this); | ||
return new Promise((resolve, reject) => { | ||
function step(key, arg) { | ||
let info, value; | ||
try { | ||
info = gen[key](arg); | ||
value = info.value; | ||
} catch (error) { | ||
reject(error); | ||
return; | ||
} | ||
if (info.done) { | ||
resolve(value); | ||
} else { | ||
return Promise.resolve(value).then( | ||
value => { | ||
step('next', value); | ||
}, | ||
err => { | ||
step('throw', err); | ||
}); | ||
} | ||
} | ||
return step('next'); | ||
}); | ||
})(function*(){ | ||
let eventTimeout, resolveCallback, rejectCallback; | ||
@@ -217,3 +245,2 @@ const promise = new Promise((resolve, reject) => { | ||
return; | ||
cleanup(); | ||
resolveCallback(event); | ||
@@ -223,3 +250,2 @@ }); | ||
eventTimeout = setTimeout(() => { | ||
cleanup(); | ||
rejectCallback(new TimeoutError('Timeout exceeded while waiting for event')); | ||
@@ -232,4 +258,13 @@ }, timeout); | ||
} | ||
return promise; | ||
} | ||
const result = (yield Promise.race([promise, abortPromise]).then(r => { | ||
cleanup(); | ||
return r; | ||
}, e => { | ||
cleanup(); | ||
throw e; | ||
})); | ||
if (result instanceof Error) | ||
throw result; | ||
return result; | ||
});} | ||
@@ -236,0 +271,0 @@ /** |
@@ -50,2 +50,72 @@ /** | ||
/** | ||
* @param {Function|String} pageFunction | ||
* @param {!Array<*>} args | ||
* @return {!Promise<(!Object|undefined)>} | ||
*/ | ||
/* async */ evaluate(pageFunction, ...args) {return (fn => { | ||
const gen = fn.call(this); | ||
return new Promise((resolve, reject) => { | ||
function step(key, arg) { | ||
let info, value; | ||
try { | ||
info = gen[key](arg); | ||
value = info.value; | ||
} catch (error) { | ||
reject(error); | ||
return; | ||
} | ||
if (info.done) { | ||
resolve(value); | ||
} else { | ||
return Promise.resolve(value).then( | ||
value => { | ||
step('next', value); | ||
}, | ||
err => { | ||
step('throw', err); | ||
}); | ||
} | ||
} | ||
return step('next'); | ||
}); | ||
})(function*(){ | ||
return (yield this.executionContext().evaluate(pageFunction, this, ...args)); | ||
});} | ||
/** | ||
* @param {Function|string} pageFunction | ||
* @param {!Array<*>} args | ||
* @return {!Promise<!Puppeteer.JSHandle>} | ||
*/ | ||
/* async */ evaluateHandle(pageFunction, ...args) {return (fn => { | ||
const gen = fn.call(this); | ||
return new Promise((resolve, reject) => { | ||
function step(key, arg) { | ||
let info, value; | ||
try { | ||
info = gen[key](arg); | ||
value = info.value; | ||
} catch (error) { | ||
reject(error); | ||
return; | ||
} | ||
if (info.done) { | ||
resolve(value); | ||
} else { | ||
return Promise.resolve(value).then( | ||
value => { | ||
step('next', value); | ||
}, | ||
err => { | ||
step('throw', err); | ||
}); | ||
} | ||
} | ||
return step('next'); | ||
}); | ||
})(function*(){ | ||
return (yield this.executionContext().evaluateHandle(pageFunction, this, ...args)); | ||
});} | ||
/** | ||
* @param {string} propertyName | ||
@@ -81,7 +151,7 @@ * @return {!Promise<?JSHandle>} | ||
})(function*(){ | ||
const objectHandle = (yield this._context.evaluateHandle((object, propertyName) => { | ||
const objectHandle = (yield this.evaluateHandle((object, propertyName) => { | ||
const result = {__proto__: null}; | ||
result[propertyName] = object[propertyName]; | ||
return result; | ||
}, this, propertyName)); | ||
}, propertyName)); | ||
const properties = (yield objectHandle.getProperties()); | ||
@@ -321,3 +391,3 @@ const result = properties.get(propertyName) || null; | ||
})(function*(){ | ||
const error = (yield this.executionContext().evaluate(/* async */(element, pageJavascriptEnabled) => {return (fn => { | ||
const error = (yield this.evaluate(/* async */(element, pageJavascriptEnabled) => {return (fn => { | ||
const gen = fn.call(this); | ||
@@ -368,3 +438,3 @@ return new Promise((resolve, reject) => { | ||
return false; | ||
});}, this, this._page._javascriptEnabled)); | ||
});}, this._page._javascriptEnabled)); | ||
if (error) | ||
@@ -534,2 +604,52 @@ throw new Error(error); | ||
/** | ||
* @param {!Array<string>} values | ||
* @return {!Promise<!Array<string>>} | ||
*/ | ||
/* async */ select(...values) {return (fn => { | ||
const gen = fn.call(this); | ||
return new Promise((resolve, reject) => { | ||
function step(key, arg) { | ||
let info, value; | ||
try { | ||
info = gen[key](arg); | ||
value = info.value; | ||
} catch (error) { | ||
reject(error); | ||
return; | ||
} | ||
if (info.done) { | ||
resolve(value); | ||
} else { | ||
return Promise.resolve(value).then( | ||
value => { | ||
step('next', value); | ||
}, | ||
err => { | ||
step('throw', err); | ||
}); | ||
} | ||
} | ||
return step('next'); | ||
}); | ||
})(function*(){ | ||
for (const value of values) | ||
assert(helper.isString(value), 'Values must be strings. Found value "' + value + '" of type "' + (typeof value) + '"'); | ||
return this.evaluate((element, values) => { | ||
if (element.nodeName.toLowerCase() !== 'select') | ||
throw new Error('Element is not a <select> element.'); | ||
const options = Array.from(element.options); | ||
element.value = undefined; | ||
for (const option of options) { | ||
option.selected = values.includes(option.value); | ||
if (option.selected && !element.multiple) | ||
break; | ||
} | ||
element.dispatchEvent(new Event('input', { 'bubbles': true })); | ||
element.dispatchEvent(new Event('change', { 'bubbles': true })); | ||
return options.filter(option => option.selected).map(option => option.value); | ||
}, values); | ||
});} | ||
/** | ||
* @param {!Array<string>} filePaths | ||
@@ -628,3 +748,3 @@ */ | ||
})(function*(){ | ||
(yield this.executionContext().evaluate(element => element.focus(), this)); | ||
(yield this.evaluate(element => element.focus())); | ||
});} | ||
@@ -895,5 +1015,5 @@ | ||
})(function*(){ | ||
const handle = (yield this.executionContext().evaluateHandle( | ||
const handle = (yield this.evaluateHandle( | ||
(element, selector) => element.querySelector(selector), | ||
this, selector | ||
selector | ||
)); | ||
@@ -938,5 +1058,5 @@ const element = handle.asElement(); | ||
})(function*(){ | ||
const arrayHandle = (yield this.executionContext().evaluateHandle( | ||
const arrayHandle = (yield this.evaluateHandle( | ||
(element, selector) => element.querySelectorAll(selector), | ||
this, selector | ||
selector | ||
)); | ||
@@ -990,3 +1110,3 @@ const properties = (yield arrayHandle.getProperties()); | ||
throw new Error(`Error: failed to find element matching selector "${selector}"`); | ||
const result = (yield this.executionContext().evaluate(pageFunction, elementHandle, ...args)); | ||
const result = (yield elementHandle.evaluate(pageFunction, ...args)); | ||
(yield elementHandle.dispose()); | ||
@@ -1029,8 +1149,8 @@ return result; | ||
})(function*(){ | ||
const arrayHandle = (yield this.executionContext().evaluateHandle( | ||
const arrayHandle = (yield this.evaluateHandle( | ||
(element, selector) => Array.from(element.querySelectorAll(selector)), | ||
this, selector | ||
selector | ||
)); | ||
const result = (yield this.executionContext().evaluate(pageFunction, arrayHandle, ...args)); | ||
const result = (yield arrayHandle.evaluate(pageFunction, ...args)); | ||
(yield arrayHandle.dispose()); | ||
@@ -1071,3 +1191,3 @@ return result; | ||
})(function*(){ | ||
const arrayHandle = (yield this.executionContext().evaluateHandle( | ||
const arrayHandle = (yield this.evaluateHandle( | ||
(element, expression) => { | ||
@@ -1082,3 +1202,3 @@ const document = element.ownerDocument || element; | ||
}, | ||
this, expression | ||
expression | ||
)); | ||
@@ -1100,3 +1220,3 @@ const properties = (yield arrayHandle.getProperties()); | ||
isIntersectingViewport() { | ||
return this.executionContext().evaluate(/* async */ element => {return (fn => { | ||
return this.evaluate(/* async */ element => {return (fn => { | ||
const gen = fn.call(this); | ||
@@ -1136,3 +1256,3 @@ return new Promise((resolve, reject) => { | ||
return visibleRatio > 0; | ||
});}, this); | ||
});}); | ||
} | ||
@@ -1139,0 +1259,0 @@ } |
@@ -49,5 +49,4 @@ /** | ||
'--disable-extensions', | ||
// TODO: Support OOOPIF. @see https://github.com/GoogleChrome/puppeteer/issues/2548 | ||
// BlinkGenPropertyTrees disabled due to crbug.com/937609 | ||
'--disable-features=site-per-process,TranslateUI,BlinkGenPropertyTrees', | ||
'--disable-features=TranslateUI,BlinkGenPropertyTrees', | ||
'--disable-hang-monitor', | ||
@@ -54,0 +53,0 @@ '--disable-ipc-flooding-protection', |
@@ -23,8 +23,9 @@ /** | ||
* @param {!Puppeteer.CDPSession} client | ||
* @param {!Puppeteer.FrameManager} frameManager | ||
*/ | ||
constructor(client, ignoreHTTPSErrors) { | ||
constructor(client, ignoreHTTPSErrors, frameManager) { | ||
super(); | ||
this._client = client; | ||
this._ignoreHTTPSErrors = ignoreHTTPSErrors; | ||
this._frameManager = null; | ||
this._frameManager = frameManager; | ||
/** @type {!Map<string, !Request>} */ | ||
@@ -91,9 +92,2 @@ this._requestIdToRequest = new Map(); | ||
/** | ||
* @param {!Puppeteer.FrameManager} frameManager | ||
*/ | ||
setFrameManager(frameManager) { | ||
this._frameManager = frameManager; | ||
} | ||
/** | ||
* @param {?{username: string, password: string}} credentials | ||
@@ -473,3 +467,3 @@ */ | ||
} | ||
const frame = event.frameId && this._frameManager ? this._frameManager.frame(event.frameId) : null; | ||
const frame = event.frameId ? this._frameManager.frame(event.frameId) : null; | ||
const request = new Request(this._client, frame, interceptionId, this._userRequestInterceptionEnabled, event, redirectChain); | ||
@@ -1119,4 +1113,6 @@ this._requestIdToRequest.set(event.requestId, request); | ||
const result = []; | ||
for (const name in headers) | ||
result.push({name, value: headers[name] + ''}); | ||
for (const name in headers) { | ||
if (!Object.is(headers[name], undefined)) | ||
result.push({name, value: headers[name] + ''}); | ||
} | ||
return result; | ||
@@ -1123,0 +1119,0 @@ } |
@@ -20,3 +20,2 @@ /** | ||
const {Worker} = require('./Worker'); | ||
const {Connection} = require('./Connection'); | ||
@@ -166,39 +165,6 @@ class Target { | ||
if (!this._workerPromise) { | ||
this._workerPromise = this._sessionFactory().then(/* async */ client => {return (fn => { | ||
const gen = fn.call(this); | ||
return new Promise((resolve, reject) => { | ||
function step(key, arg) { | ||
let info, value; | ||
try { | ||
info = gen[key](arg); | ||
value = info.value; | ||
} catch (error) { | ||
reject(error); | ||
return; | ||
} | ||
if (info.done) { | ||
resolve(value); | ||
} else { | ||
return Promise.resolve(value).then( | ||
value => { | ||
step('next', value); | ||
}, | ||
err => { | ||
step('throw', err); | ||
}); | ||
} | ||
// TODO(einbinder): Make workers send their console logs. | ||
this._workerPromise = this._sessionFactory() | ||
.then(client => new Worker(client, this._targetInfo.url, () => {} /* consoleAPICalled */, () => {} /* exceptionThrown */)); | ||
} | ||
return step('next'); | ||
}); | ||
})(function*(){ | ||
// Top level workers have a fake page wrapping the actual worker. | ||
const [targetAttached] = (yield Promise.all([ | ||
new Promise(x => client.once('Target.attachedToTarget', x)), | ||
client.send('Target.setAutoAttach', {autoAttach: true, waitForDebuggerOnStart: false, flatten: true}), | ||
])); | ||
const session = Connection.fromSession(client).session(targetAttached.sessionId); | ||
// TODO(einbinder): Make workers send their console logs. | ||
return new Worker(session, this._targetInfo.url, () => {} /* consoleAPICalled */, () => {} /* exceptionThrown */); | ||
});}); | ||
} | ||
return this._workerPromise; | ||
@@ -205,0 +171,0 @@ });} |
{ | ||
"name": "puppeteer-core", | ||
"version": "1.19.0", | ||
"version": "1.20.0", | ||
"description": "A high-level API to control headless Chrome over the DevTools Protocol", | ||
@@ -11,3 +11,3 @@ "main": "index.js", | ||
"puppeteer": { | ||
"chromium_revision": "674921" | ||
"chromium_revision": "686378" | ||
}, | ||
@@ -19,3 +19,3 @@ "scripts": { | ||
"test-doclint": "node utils/doclint/check_public_api/test/test.js && node utils/doclint/preprocessor/test.js", | ||
"test": "npm run lint --silent && npm run coverage && npm run test-doclint && npm run test-node6-transformer && npm run test-types", | ||
"test": "npm run lint --silent && npm run coverage && npm run test-doclint && npm run test-node6-transformer && npm run test-types && node utils/testrunner/test/test.js", | ||
"install": "node install.js", | ||
@@ -22,0 +22,0 @@ "lint": "([ \"$CI\" = true ] && eslint --quiet -f codeframe . || eslint .) && npm run tsc && npm run doc", |
# 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) | ||
[![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) | ||
<!-- [END badges] --> | ||
@@ -9,3 +9,3 @@ | ||
###### [API](https://github.com/GoogleChrome/puppeteer/blob/v1.19.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/GoogleChrome/puppeteer/blob/v1.20.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) | ||
@@ -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/v1.19.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/GoogleChrome/puppeteer/blob/v1.20.0/docs/api.md#environment-variables). | ||
@@ -65,3 +65,3 @@ | ||
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/v1.19.0/docs/api.md#). | ||
of `Browser`, open pages, and then manipulate them with [Puppeteer's API](https://github.com/GoogleChrome/puppeteer/blob/v1.20.0/docs/api.md#). | ||
@@ -91,3 +91,3 @@ **Example** - navigating to https://example.com and saving a screenshot as *example.png*: | ||
Puppeteer sets an initial page size to 800px x 600px, which defines the screenshot size. The page size can be customized with [`Page.setViewport()`](https://github.com/GoogleChrome/puppeteer/blob/v1.19.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/GoogleChrome/puppeteer/blob/v1.20.0/docs/api.md#pagesetviewportviewport). | ||
@@ -117,3 +117,3 @@ **Example** - create a PDF. | ||
See [`Page.pdf()`](https://github.com/GoogleChrome/puppeteer/blob/v1.19.0/docs/api.md#pagepdfoptions) for more information about creating pdfs. | ||
See [`Page.pdf()`](https://github.com/GoogleChrome/puppeteer/blob/v1.20.0/docs/api.md#pagepdfoptions) for more information about creating pdfs. | ||
@@ -153,3 +153,3 @@ **Example** - evaluate script in the context of the page | ||
See [`Page.evaluate()`](https://github.com/GoogleChrome/puppeteer/blob/v1.19.0/docs/api.md#pageevaluatepagefunction-args) for more information on `evaluate` and related methods like `evaluateOnNewDocument` and `exposeFunction`. | ||
See [`Page.evaluate()`](https://github.com/GoogleChrome/puppeteer/blob/v1.20.0/docs/api.md#pageevaluatepagefunction-args) for more information on `evaluate` and related methods like `evaluateOnNewDocument` and `exposeFunction`. | ||
@@ -163,3 +163,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/v1.19.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/GoogleChrome/puppeteer/blob/v1.20.0/docs/api.md#puppeteerlaunchoptions) when launching a browser: | ||
@@ -180,3 +180,3 @@ ```js | ||
See [`Puppeteer.launch()`](https://github.com/GoogleChrome/puppeteer/blob/v1.19.0/docs/api.md#puppeteerlaunchoptions) for more information. | ||
See [`Puppeteer.launch()`](https://github.com/GoogleChrome/puppeteer/blob/v1.20.0/docs/api.md#puppeteerlaunchoptions) for more information. | ||
@@ -193,3 +193,3 @@ 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/v1.19.0/docs/api.md) | ||
- [API Documentation](https://github.com/GoogleChrome/puppeteer/blob/v1.20.0/docs/api.md) | ||
- [Examples](https://github.com/GoogleChrome/puppeteer/tree/master/examples/) | ||
@@ -352,3 +352,3 @@ - [Community list of Puppeteer resources](https://github.com/transitive-bullshit/awesome-puppeteer) | ||
Look for `chromium_revision` in [package.json](https://github.com/GoogleChrome/puppeteer/blob/master/package.json). | ||
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. | ||
@@ -385,3 +385,3 @@ #### Q: What’s considered a “Navigation”? | ||
* 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/v1.19.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/GoogleChrome/puppeteer/blob/v1.20.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). | ||
@@ -388,0 +388,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
767886
73
23077
25