puppeteer
Advanced tools
Comparing version 0.13.0 to 1.0.0-next.1514510476874
@@ -74,3 +74,3 @@ module.exports = { | ||
"valid-typeof": 2, | ||
"no-unused-vars": [2, { "args": "none", "vars": "local" }], | ||
"no-unused-vars": [2, { "args": "none", "vars": "local", "varsIgnorePattern": "([fx]?describe|[fx]?it|beforeAll|beforeEach|afterAll|afterEach)" }], | ||
"no-implicit-globals": [2], | ||
@@ -77,0 +77,0 @@ |
@@ -66,3 +66,3 @@ # How to Contribute | ||
- `style` - puppeteer code style: spaces/alignment/wrapping etc | ||
- `chore` - build-related work, e.g. doclint changes / travis / appveyour | ||
- `chore` - build-related work, e.g. doclint changes / travis / appveyor | ||
1. *namespace* is put in parenthesis after label and is optional | ||
@@ -115,3 +115,4 @@ 2. *title* is a brief summary of changes | ||
Puppeteer tests are located in [test/test.js](https://github.com/GoogleChrome/puppeteer/blob/master/test/test.js) | ||
and are written using [Jasmine](https://jasmine.github.io/) testing framework. Despite being named 'unit', these are integration tests, making sure public API methods and events work as expected. | ||
and are written with a [TestRunner](https://github.com/GoogleChrome/puppeteer/tree/master/utils/testrunner) framework. | ||
Despite being named 'unit', these are integration tests, making sure public API methods and events work as expected. | ||
@@ -118,0 +119,0 @@ - To run all tests: |
@@ -331,2 +331,26 @@ /** | ||
{ | ||
'name': 'iPhone X', | ||
'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1', | ||
'viewport': { | ||
'width': 375, | ||
'height': 812, | ||
'deviceScaleFactor': 3, | ||
'isMobile': true, | ||
'hasTouch': true, | ||
'isLandscape': false | ||
} | ||
}, | ||
{ | ||
'name': 'iPhone X landscape', | ||
'userAgent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1', | ||
'viewport': { | ||
'width': 812, | ||
'height': 375, | ||
'deviceScaleFactor': 3, | ||
'isMobile': true, | ||
'hasTouch': true, | ||
'isLandscape': true | ||
} | ||
}, | ||
{ | ||
'name': 'Kindle Fire HDX', | ||
@@ -333,0 +357,0 @@ 'userAgent': 'Mozilla/5.0 (Linux; U; en-us; KFAPWI Build/JDQ39) AppleWebKit/535.19 (KHTML, like Gecko) Silk/3.13 Safari/535.19 Silk-Accelerated=true', |
@@ -27,3 +27,3 @@ /** | ||
page.on('request', request => { | ||
if (request.resourceType === 'image') | ||
if (request.resourceType() === 'image') | ||
request.abort(); | ||
@@ -30,0 +30,0 @@ else |
@@ -14,3 +14,3 @@ # Running the examples | ||
By default, Puppeteer disables extensions when launching Chrome. You can load a specific | ||
extension using: | ||
extension using: | ||
@@ -35,2 +35,3 @@ ```js | ||
- [pupperender](https://github.com/LasaleFamine/pupperender) - Express middleware that checks the User-Agent header of incoming requests, and if it matches one of a configurable set of bots, render the page using Puppeteer. Useful for PWA rendering. | ||
- [headless-chrome-crawler](https://github.com/yujiosaka/headless-chrome-crawler) - Crawler that provides simple APIs to manipulate Headless Chrome and allows you to crawl dynamic websites. | ||
@@ -37,0 +38,0 @@ ## Testing |
@@ -17,2 +17,7 @@ /** | ||
/** | ||
* @fileoverview Search developers.google.com/web for articles tagged | ||
* "Headless Chrome" and scrape results from the results page. | ||
*/ | ||
'use strict'; | ||
@@ -26,21 +31,29 @@ | ||
const page = await browser.newPage(); | ||
await page.goto('https://google.com', {waitUntil: 'networkidle2'}); | ||
await page.waitFor('input[name=q]'); | ||
// Type our query into the search bar | ||
await page.type('input[name=q]', 'puppeteer'); | ||
await page.goto('https://developers.google.com/web/'); | ||
await page.click('input[type="submit"]'); | ||
// Type into search box. | ||
await page.type('#searchbox input', 'Headless Chrome'); | ||
// Wait for the results to show up | ||
await page.waitForSelector('h3 a'); | ||
// Wait for suggest overlay to appear and click "show all results". | ||
const allResultsSelector = '.devsite-suggest-all-results'; | ||
await page.waitForSelector(allResultsSelector); | ||
await page.click(allResultsSelector); | ||
// Extract the results from the page | ||
const links = await page.evaluate(() => { | ||
const anchors = Array.from(document.querySelectorAll('h3 a')); | ||
return anchors.map(anchor => anchor.textContent); | ||
}); | ||
// Wait for the results page to load and display the results. | ||
const resultsSelector = '.gsc-results .gsc-thumbnail-inside a.gs-title'; | ||
await page.waitForSelector(resultsSelector); | ||
// Extract the results from the page. | ||
const links = await page.evaluate(resultsSelector => { | ||
const anchors = Array.from(document.querySelectorAll(resultsSelector)); | ||
return anchors.map(anchor => { | ||
const title = anchor.textContent.split('|')[0].trim(); | ||
return `${title} - ${anchor.href}`; | ||
}); | ||
}, resultsSelector); | ||
console.log(links.join('\n')); | ||
await browser.close(); | ||
})(); |
11
index.js
@@ -17,10 +17,13 @@ /** | ||
// If node does not support async await, use the compiled version. | ||
let folder = 'lib'; | ||
let asyncawait = true; | ||
try { | ||
new Function('async function test(){await 1}'); | ||
} catch (error) { | ||
folder = 'node6'; | ||
asyncawait = false; | ||
} | ||
module.exports = require(`./${folder}/Puppeteer`); | ||
// If node does not support async await, use the compiled version. | ||
if (asyncawait) | ||
module.exports = require('./lib/Puppeteer'); | ||
else | ||
module.exports = require('./node6/lib/Puppeteer'); |
@@ -17,2 +17,4 @@ /** | ||
buildNode6IfNecessary(); | ||
if (process.env.PUPPETEER_SKIP_CHROMIUM_DOWNLOAD) { | ||
@@ -27,8 +29,10 @@ console.log('**INFO** Skipping Chromium download. "PUPPETEER_SKIP_CHROMIUM_DOWNLOAD" environment variable was found.'); | ||
const Downloader = require('./utils/ChromiumDownloader'); | ||
const platform = Downloader.currentPlatform(); | ||
const revision = require('./package').puppeteer.chromium_revision; | ||
const Downloader = require('./lib/Downloader'); | ||
const downloader = Downloader.createDefault(); | ||
const platform = downloader.currentPlatform(); | ||
const revision = Downloader.defaultRevision(); | ||
const ProgressBar = require('progress'); | ||
const revisionInfo = Downloader.revisionInfo(platform, revision); | ||
const revisionInfo = downloader.revisionInfo(platform, revision); | ||
// Do nothing if the revision is already downloaded. | ||
@@ -50,5 +54,7 @@ if (revisionInfo.downloaded) | ||
const allRevisions = Downloader.downloadedRevisions(); | ||
const DOWNLOAD_HOST = process.env.PUPPETEER_DOWNLOAD_HOST || process.env.npm_config_puppeteer_download_host; | ||
Downloader.downloadRevision(platform, revision, DOWNLOAD_HOST, onProgress) | ||
const allRevisions = downloader.downloadedRevisions(); | ||
const downloadHost = process.env.PUPPETEER_DOWNLOAD_HOST || process.env.npm_config_puppeteer_download_host; | ||
if (downloadHost) | ||
downloader.setDownloadHost(downloadHost); | ||
downloader.downloadRevision(platform, revision, onProgress) | ||
.then(onSuccess) | ||
@@ -63,3 +69,3 @@ .catch(onError); | ||
// Remove previous chromium revisions. | ||
const cleanupOldVersions = allRevisions.map(({platform, revision}) => Downloader.removeRevision(platform, revision)); | ||
const cleanupOldVersions = allRevisions.map(({platform, revision}) => downloader.removeRevision(platform, revision)); | ||
return Promise.all(cleanupOldVersions); | ||
@@ -95,1 +101,22 @@ } | ||
function buildNode6IfNecessary() { | ||
const fs = require('fs'); | ||
const path = require('path'); | ||
// if this package is installed from NPM, then it already has up-to-date node6 | ||
// folder. | ||
if (!fs.existsSync(path.join('utils', 'node6-transform'))) | ||
return; | ||
let asyncawait = true; | ||
try { | ||
new Function('async function test(){await 1}'); | ||
} catch (error) { | ||
asyncawait = false; | ||
} | ||
// if async/await is supported, then node6 is not needed. | ||
if (asyncawait) | ||
return; | ||
// Re-build node6/ folder. | ||
console.log('Building Puppeteer for Node 6'); | ||
require(path.join(__dirname, 'utils', 'node6-transform')); | ||
} |
@@ -25,8 +25,10 @@ /** | ||
* @param {!Object=} options | ||
* @param {?Puppeteer.ChildProcess} process | ||
* @param {(function():Promise)=} closeCallback | ||
*/ | ||
constructor(connection, options = {}, closeCallback) { | ||
constructor(connection, options = {}, process, closeCallback) { | ||
super(); | ||
this._ignoreHTTPSErrors = !!options.ignoreHTTPSErrors; | ||
this._appMode = !!options.appMode; | ||
this._process = process; | ||
this._screenshotTaskQueue = new TaskQueue(); | ||
@@ -46,8 +48,16 @@ this._connection = connection; | ||
/** | ||
* @return {?Puppeteer.ChildProcess} | ||
*/ | ||
process() { | ||
return this._process; | ||
} | ||
/** | ||
* @param {!Puppeteer.Connection} connection | ||
* @param {boolean} ignoreHTTPSErrors | ||
* @param {!Object=} options | ||
* @param {?Puppeteer.ChildProcess} process | ||
* @param {function()=} closeCallback | ||
*/ | ||
static async create(connection, ignoreHTTPSErrors, closeCallback) { | ||
const browser = new Browser(connection, ignoreHTTPSErrors, closeCallback); | ||
static async create(connection, options, process, closeCallback) { | ||
const browser = new Browser(connection, options, process, closeCallback); | ||
await connection.send('Target.setDiscoverTargets', {discover: true}); | ||
@@ -54,0 +64,0 @@ return browser; |
@@ -45,3 +45,3 @@ /** | ||
this._lastId = 0; | ||
/** @type {!Map<number, {resolve: function, reject: function, method: string}>}*/ | ||
/** @type {!Map<number, {resolve: function, reject: function, error: !Error, method: string}>}*/ | ||
this._callbacks = new Map(); | ||
@@ -75,3 +75,3 @@ this._delay = delay; | ||
return new Promise((resolve, reject) => { | ||
this._callbacks.set(id, {resolve, reject, method}); | ||
this._callbacks.set(id, {resolve, reject, error: new Error(), method}); | ||
}); | ||
@@ -99,3 +99,3 @@ } | ||
if (object.error) | ||
callback.reject(new Error(`Protocol error (${callback.method}): ${object.error.message} ${object.error.data}`)); | ||
callback.reject(rewriteError(callback.error, `Protocol error (${callback.method}): ${object.error.message} ${object.error.data}`)); | ||
else | ||
@@ -127,3 +127,3 @@ callback.resolve(object.result); | ||
for (const callback of this._callbacks.values()) | ||
callback.reject(new Error(`Protocol error (${callback.method}): Target closed.`)); | ||
callback.reject(rewriteError(callback.error, `Protocol error (${callback.method}): Target closed.`)); | ||
this._callbacks.clear(); | ||
@@ -161,3 +161,3 @@ for (const session of this._sessions.values()) | ||
this._lastId = 0; | ||
/** @type {!Map<number, {resolve: function, reject: function, method: string}>}*/ | ||
/** @type {!Map<number, {resolve: function, reject: function, error: !Error, method: string}>}*/ | ||
this._callbacks = new Map(); | ||
@@ -193,6 +193,6 @@ this._connection = connection; | ||
this._callbacks.delete(id); | ||
callback.reject(e); | ||
callback.reject(rewriteError(callback.error, e && e.message)); | ||
}); | ||
return new Promise((resolve, reject) => { | ||
this._callbacks.set(id, {resolve, reject, method}); | ||
this._callbacks.set(id, {resolve, reject, error: new Error(), method}); | ||
}); | ||
@@ -211,3 +211,3 @@ } | ||
if (object.error) | ||
callback.reject(new Error(`Protocol error (${callback.method}): ${object.error.message} ${object.error.data}`)); | ||
callback.reject(rewriteError(callback.error, `Protocol error (${callback.method}): ${object.error.message} ${object.error.data}`)); | ||
else | ||
@@ -228,3 +228,3 @@ callback.resolve(object.result); | ||
for (const callback of this._callbacks.values()) | ||
callback.reject(new Error(`Protocol error (${callback.method}): Target closed.`)); | ||
callback.reject(rewriteError(callback.error, `Protocol error (${callback.method}): Target closed.`)); | ||
this._callbacks.clear(); | ||
@@ -235,2 +235,12 @@ this._connection = null; | ||
/** | ||
* @param {!Error} error | ||
* @param {string} message | ||
* @return {!Error} | ||
*/ | ||
function rewriteError(error, message) { | ||
error.message = message; | ||
return error; | ||
} | ||
module.exports = {Connection, Session}; |
@@ -28,3 +28,3 @@ /** | ||
this._client = client; | ||
this.type = type; | ||
this._type = type; | ||
this._message = message; | ||
@@ -38,2 +38,9 @@ this._handled = false; | ||
*/ | ||
type() { | ||
return this._type; | ||
} | ||
/** | ||
* @return {string} | ||
*/ | ||
message() { | ||
@@ -40,0 +47,0 @@ return this._message; |
@@ -195,2 +195,21 @@ /** | ||
} | ||
/** | ||
* @param {string} expression | ||
* @return {!Promise<?ElementHandle>} | ||
*/ | ||
async xpath(expression) { | ||
const handle = await this.executionContext().evaluateHandle( | ||
(element, expression) => { | ||
const document = element.ownerDocument || element; | ||
return document.evaluate(expression, element, null, XPathResult.FIRST_ORDERED_NODE_TYPE).singleNodeValue; | ||
}, | ||
this, expression | ||
); | ||
const element = handle.asElement(); | ||
if (element) | ||
return element; | ||
await handle.dispose(); | ||
return null; | ||
} | ||
} | ||
@@ -197,0 +216,0 @@ |
@@ -22,8 +22,12 @@ /** | ||
* @param {!Puppeteer.Session} client | ||
* @param {string} contextId | ||
* @param {!Object} contextPayload | ||
* @param {function(*):!JSHandle} objectHandleFactory | ||
*/ | ||
constructor(client, contextId, objectHandleFactory) { | ||
constructor(client, contextPayload, objectHandleFactory) { | ||
this._client = client; | ||
this._contextId = contextId; | ||
this._contextId = contextPayload.id; | ||
const auxData = contextPayload.auxData || {isDefault: true}; | ||
this._frameId = auxData.frameId || null; | ||
this._isDefault = !!auxData.isDefault; | ||
this._objectHandleFactory = objectHandleFactory; | ||
@@ -33,3 +37,3 @@ } | ||
/** | ||
* @param {function(*)|string} pageFunction | ||
* @param {Function|string} pageFunction | ||
* @param {...*} args | ||
@@ -46,3 +50,3 @@ * @return {!Promise<(!Object|undefined)>} | ||
/** | ||
* @param {function(*)|string} pageFunction | ||
* @param {Function|string} pageFunction | ||
* @param {...*} args | ||
@@ -49,0 +53,0 @@ * @return {!Promise<!JSHandle>} |
@@ -44,2 +44,4 @@ /** | ||
this._client.on('Runtime.executionContextCreated', event => this._onExecutionContextCreated(event.context)); | ||
this._client.on('Runtime.executionContextDestroyed', event => this._onExecutionContextDestroyed(event.executionContextId)); | ||
this._client.on('Runtime.executionContextsCleared', event => this._onExecutionContextsCleared()); | ||
this._client.on('Page.lifecycleEvent', event => this._onLifecycleEvent(event)); | ||
@@ -148,16 +150,34 @@ | ||
_onExecutionContextCreated(contextPayload) { | ||
const context = new ExecutionContext(this._client, contextPayload.id, this.createJSHandle.bind(this, contextPayload.id)); | ||
const context = new ExecutionContext(this._client, contextPayload, this.createJSHandle.bind(this, contextPayload.id)); | ||
this._contextIdToContext.set(contextPayload.id, context); | ||
const frameId = contextPayload.auxData && contextPayload.auxData.isDefault ? contextPayload.auxData.frameId : null; | ||
const frame = this._frames.get(frameId); | ||
if (!frame) | ||
const frame = context._frameId ? this._frames.get(context._frameId) : null; | ||
if (frame && context._isDefault) | ||
frame._setDefaultContext(context); | ||
} | ||
/** | ||
* @param {!ExecutionContext} context | ||
*/ | ||
_removeContext(context) { | ||
const frame = context._frameId ? this._frames.get(context._frameId) : null; | ||
if (frame && context._isDefault) | ||
frame._setDefaultContext(null); | ||
} | ||
/** | ||
* @param {string} executionContextId | ||
*/ | ||
_onExecutionContextDestroyed(executionContextId) { | ||
const context = this._contextIdToContext.get(executionContextId); | ||
if (!context) | ||
return; | ||
frame._context = context; | ||
for (const waitTask of frame._waitTasks) | ||
waitTask.rerun(); | ||
this._contextIdToContext.delete(executionContextId); | ||
this._removeContext(context); | ||
} | ||
_onExecutionContextDestroyed(contextPayload) { | ||
this._contextIdToContext.delete(contextPayload.id); | ||
_onExecutionContextsCleared() { | ||
for (const context of this._contextIdToContext.values()) | ||
this._removeContext(context); | ||
this._contextIdToContext.clear(); | ||
} | ||
@@ -213,3 +233,10 @@ | ||
this._id = frameId; | ||
this._context = null; | ||
/** @type {?Promise<!ElementHandle>} */ | ||
this._documentPromise = null; | ||
/** @type {?Promise<!ExecutionContext>} */ | ||
this._contextPromise = null; | ||
this._contextResolveCallback = null; | ||
this._setDefaultContext(null); | ||
/** @type {!Set<!WaitTask>} */ | ||
@@ -228,6 +255,23 @@ this._waitTasks = new Set(); | ||
/** | ||
* @return {!ExecutionContext} | ||
* @param {?ExecutionContext} context | ||
*/ | ||
_setDefaultContext(context) { | ||
if (context) { | ||
this._contextResolveCallback.call(null, context); | ||
this._contextResolveCallback = null; | ||
for (const waitTask of this._waitTasks) | ||
waitTask.rerun(); | ||
} else { | ||
this._documentPromise = null; | ||
this._contextPromise = new Promise(fulfill => { | ||
this._contextResolveCallback = fulfill; | ||
}); | ||
} | ||
} | ||
/** | ||
* @return {!Promise<!ExecutionContext>} | ||
*/ | ||
executionContext() { | ||
return this._context; | ||
return this._contextPromise; | ||
} | ||
@@ -241,3 +285,4 @@ | ||
async evaluate(pageFunction, ...args) { | ||
return this._context.evaluate(pageFunction, ...args); | ||
const context = await this._contextPromise; | ||
return context.evaluate(pageFunction, ...args); | ||
} | ||
@@ -250,11 +295,31 @@ | ||
async $(selector) { | ||
const handle = await this._context.evaluateHandle(selector => document.querySelector(selector), selector); | ||
const element = handle.asElement(); | ||
if (element) | ||
return element; | ||
await handle.dispose(); | ||
return null; | ||
const document = await this._document(); | ||
const value = await document.$(selector); | ||
return value; | ||
} | ||
/** | ||
* @return {!Promise<!ElementHandle>} | ||
*/ | ||
async _document() { | ||
if (this._documentPromise) | ||
return this._documentPromise; | ||
this._documentPromise = this._contextPromise.then(async context => { | ||
const document = await context.evaluateHandle('document'); | ||
return document.asElement(); | ||
}); | ||
return this._documentPromise; | ||
} | ||
/** | ||
* @param {string} expression | ||
* @return {!Promise<?ElementHandle>} | ||
*/ | ||
async xpath(expression) { | ||
const document = await this._document(); | ||
const value = await document.xpath(expression); | ||
return value; | ||
} | ||
/** | ||
* @param {string} selector | ||
@@ -281,3 +346,4 @@ * @param {Function|string} pageFunction | ||
async $$eval(selector, pageFunction, ...args) { | ||
const arrayHandle = await this._context.evaluateHandle(selector => Array.from(document.querySelectorAll(selector)), selector); | ||
const context = await this._contextPromise; | ||
const arrayHandle = await context.evaluateHandle(selector => Array.from(document.querySelectorAll(selector)), selector); | ||
const result = await this.evaluate(pageFunction, arrayHandle, ...args); | ||
@@ -293,15 +359,33 @@ await arrayHandle.dispose(); | ||
async $$(selector) { | ||
const arrayHandle = await this._context.evaluateHandle(selector => document.querySelectorAll(selector), selector); | ||
const properties = await arrayHandle.getProperties(); | ||
await arrayHandle.dispose(); | ||
const result = []; | ||
for (const property of properties.values()) { | ||
const elementHandle = property.asElement(); | ||
if (elementHandle) | ||
result.push(elementHandle); | ||
} | ||
return result; | ||
const document = await this._document(); | ||
const value = await document.$$(selector); | ||
return value; | ||
} | ||
/** | ||
* @return {!Promise<String>} | ||
*/ | ||
async content() { | ||
return await this.evaluate(() => { | ||
let retVal = ''; | ||
if (document.doctype) | ||
retVal = new XMLSerializer().serializeToString(document.doctype); | ||
if (document.documentElement) | ||
retVal += document.documentElement.outerHTML; | ||
return retVal; | ||
}); | ||
} | ||
/** | ||
* @param {string} html | ||
*/ | ||
async setContent(html) { | ||
await this.evaluate(html => { | ||
document.open(); | ||
document.write(html); | ||
document.close(); | ||
}, html); | ||
} | ||
/** | ||
* @return {string} | ||
@@ -349,3 +433,4 @@ */ | ||
try { | ||
return await this._context.evaluateHandle(addScriptUrl, url); | ||
const context = await this._contextPromise; | ||
return (await context.evaluateHandle(addScriptUrl, url)).asElement(); | ||
} catch (error) { | ||
@@ -359,9 +444,11 @@ throw new Error(`Loading script from ${url} failed`); | ||
contents += '//# sourceURL=' + options.path.replace(/\n/g, ''); | ||
const context = await this._contextPromise; | ||
return (await context.evaluateHandle(addScriptContent, contents)).asElement(); | ||
} | ||
return this._context.evaluateHandle(addScriptContent, contents); | ||
if (typeof options.content === 'string') { | ||
const context = await this._contextPromise; | ||
return (await context.evaluateHandle(addScriptContent, options.content)).asElement(); | ||
} | ||
if (typeof options.content === 'string') | ||
return this._context.evaluateHandle(addScriptContent, options.content); | ||
throw new Error('Provide an object with a `url`, `path` or `content` property'); | ||
@@ -405,3 +492,4 @@ | ||
try { | ||
return await this._context.evaluateHandle(addStyleUrl, url); | ||
const context = await this._contextPromise; | ||
return (await context.evaluateHandle(addStyleUrl, url)).asElement(); | ||
} catch (error) { | ||
@@ -415,9 +503,11 @@ throw new Error(`Loading style from ${url} failed`); | ||
contents += '/*# sourceURL=' + options.path.replace(/\n/g, '') + '*/'; | ||
const context = await this._contextPromise; | ||
return (await context.evaluateHandle(addStyleContent, contents)).asElement(); | ||
} | ||
return await this._context.evaluateHandle(addStyleContent, contents); | ||
if (typeof options.content === 'string') { | ||
const context = await this._contextPromise; | ||
return (await context.evaluateHandle(addStyleContent, options.content)).asElement(); | ||
} | ||
if (typeof options.content === 'string') | ||
return await this._context.evaluateHandle(addStyleContent, options.content); | ||
throw new Error('Provide an object with a `url`, `path` or `content` property'); | ||
@@ -424,0 +514,0 @@ |
@@ -20,3 +20,3 @@ /** | ||
const childProcess = require('child_process'); | ||
const Downloader = require('../utils/ChromiumDownloader'); | ||
const Downloader = require('./Downloader'); | ||
const {Connection} = require('./Connection'); | ||
@@ -27,4 +27,3 @@ const {Browser} = require('./Browser'); | ||
const {helper} = require('./helper'); | ||
// @ts-ignore | ||
const ChromiumRevision = require('../package.json').puppeteer.chromium_revision; | ||
const ChromiumRevision = Downloader.defaultRevision(); | ||
@@ -67,6 +66,9 @@ const mkdtempAsync = helper.promisify(fs.mkdtemp); | ||
let temporaryUserDataDir = null; | ||
const chromeArguments = [].concat(DEFAULT_ARGS); | ||
const chromeArguments = []; | ||
if (!options.ignoreDefaultArgs) | ||
chromeArguments.push(...DEFAULT_ARGS); | ||
if (options.appMode) | ||
options.headless = false; | ||
else | ||
else if (!options.ignoreDefaultArgs) | ||
chromeArguments.push(...AUTOMATION_ARGS); | ||
@@ -94,3 +96,4 @@ | ||
if (typeof chromeExecutable !== 'string') { | ||
const revisionInfo = Downloader.revisionInfo(Downloader.currentPlatform(), ChromiumRevision); | ||
const downloader = Downloader.createDefault(); | ||
const revisionInfo = downloader.revisionInfo(downloader.currentPlatform(), ChromiumRevision); | ||
console.assert(revisionInfo.downloaded, `Chromium revision is not downloaded. Run "npm install"`); | ||
@@ -133,2 +136,6 @@ chromeExecutable = revisionInfo.executablePath; | ||
listeners.push(helper.addEventListener(process, 'SIGINT', forceKillChrome)); | ||
if (options.handleSIGTERM !== false) | ||
listeners.push(helper.addEventListener(process, 'SIGTERM', killChrome)); | ||
if (options.handleSIGHUP !== false) | ||
listeners.push(helper.addEventListener(process, 'SIGHUP', killChrome)); | ||
/** @type {?Connection} */ | ||
@@ -140,5 +147,5 @@ let connection = null; | ||
connection = await Connection.create(browserWSEndpoint, connectionDelay); | ||
return Browser.create(connection, options, killChrome); | ||
return Browser.create(connection, options, chromeProcess, killChrome); | ||
} catch (e) { | ||
killChrome(); | ||
forceKillChrome(); | ||
throw e; | ||
@@ -178,6 +185,14 @@ } | ||
/** | ||
* @return {!Array<string>} | ||
*/ | ||
static defaultArgs() { | ||
return DEFAULT_ARGS.concat(AUTOMATION_ARGS); | ||
} | ||
/** | ||
* @return {string} | ||
*/ | ||
static executablePath() { | ||
const revisionInfo = Downloader.revisionInfo(Downloader.currentPlatform(), ChromiumRevision); | ||
const downloader = Downloader.createDefault(); | ||
const revisionInfo = downloader.revisionInfo(downloader.currentPlatform(), ChromiumRevision); | ||
return revisionInfo.executablePath; | ||
@@ -192,3 +207,3 @@ } | ||
const connection = await Connection.create(options.browserWSEndpoint); | ||
return Browser.create(connection, options, () => connection.send('Browser.close')); | ||
return Browser.create(connection, options, null, () => connection.send('Browser.close')); | ||
} | ||
@@ -195,0 +210,0 @@ } |
@@ -46,3 +46,4 @@ /** | ||
this._eventListeners = [ | ||
helper.addEventListener(this._frameManager, FrameManager.Events.LifecycleEvent, this._checkLifecycleComplete.bind(this)) | ||
helper.addEventListener(this._frameManager, FrameManager.Events.LifecycleEvent, this._checkLifecycleComplete.bind(this)), | ||
helper.addEventListener(this._frameManager, FrameManager.Events.FrameDetached, this._checkLifecycleComplete.bind(this)) | ||
]; | ||
@@ -63,7 +64,7 @@ | ||
/** | ||
* @return {?Promise<?Error>} | ||
* @return {!Promise<?Error>} | ||
*/ | ||
_createTimeoutPromise() { | ||
if (!this._timeout) | ||
return null; | ||
return new Promise(() => {}); | ||
const errorMessage = 'Navigation Timeout Exceeded: ' + this._timeout + 'ms exceeded'; | ||
@@ -70,0 +71,0 @@ return new Promise(fulfill => this._maximumTimer = setTimeout(fulfill, this._timeout)) |
@@ -150,6 +150,6 @@ /** | ||
if (event.redirectStatusCode) { | ||
if (event.redirectUrl) { | ||
const request = this._interceptionIdToRequest.get(event.interceptionId); | ||
console.assert(request, 'INTERNAL ERROR: failed to find request for interception redirect.'); | ||
this._handleRequestRedirect(request, event.redirectStatusCode, event.redirectHeaders); | ||
this._handleRequestRedirect(request, event.responseStatusCode, event.responseHeaders); | ||
this._handleRequestStart(request._requestId, event.interceptionId, event.redirectUrl, event.resourceType, event.request); | ||
@@ -222,3 +222,5 @@ return; | ||
const request = this._requestIdToRequest.get(event.requestId); | ||
this._handleRequestRedirect(request, event.redirectResponse.status, event.redirectResponse.headers); | ||
// If we connect late to the target, we could have missed the requestWillBeSent event. | ||
if (request) | ||
this._handleRequestRedirect(request, event.redirectResponse.status, event.redirectResponse.headers); | ||
} | ||
@@ -297,12 +299,47 @@ this._handleRequestStart(event.requestId, null, event.request.url, event.type, event.request); | ||
this.url = url; | ||
this.resourceType = resourceType.toLowerCase(); | ||
this.method = payload.method; | ||
this.postData = payload.postData; | ||
this.headers = {}; | ||
this._url = url; | ||
this._resourceType = resourceType.toLowerCase(); | ||
this._method = payload.method; | ||
this._postData = payload.postData; | ||
this._headers = {}; | ||
for (const key of Object.keys(payload.headers)) | ||
this.headers[key.toLowerCase()] = payload.headers[key]; | ||
this._headers[key.toLowerCase()] = payload.headers[key]; | ||
} | ||
/** | ||
* @return {string} | ||
*/ | ||
url() { | ||
return this._url; | ||
} | ||
/** | ||
* @return {string} | ||
*/ | ||
resourceType() { | ||
return this._resourceType; | ||
} | ||
/** | ||
* @return {string} | ||
*/ | ||
method() { | ||
return this._method; | ||
} | ||
/** | ||
* @return {string} | ||
*/ | ||
postData() { | ||
return this._postData; | ||
} | ||
/** | ||
* @return {!Object} | ||
*/ | ||
headers() { | ||
return this._headers; | ||
} | ||
/** | ||
* @return {?Response} | ||
@@ -350,3 +387,3 @@ */ | ||
// Mocking responses for dataURL requests is not currently supported. | ||
if (this.url.startsWith('data:')) | ||
if (this._url.startsWith('data:')) | ||
return; | ||
@@ -443,11 +480,38 @@ console.assert(this._allowInterception, 'Request Interception is not enabled!'); | ||
this.status = status; | ||
this.ok = status >= 200 && status <= 299; | ||
this.url = request.url; | ||
this.headers = {}; | ||
this._status = status; | ||
this._url = request.url(); | ||
this._headers = {}; | ||
for (const key of Object.keys(headers)) | ||
this.headers[key.toLowerCase()] = headers[key]; | ||
this._headers[key.toLowerCase()] = headers[key]; | ||
} | ||
/** | ||
* @return {string} | ||
*/ | ||
url() { | ||
return this._url; | ||
} | ||
/** | ||
* @return {boolean} | ||
*/ | ||
ok() { | ||
return this._status >= 200 && this._status <= 299; | ||
} | ||
/** | ||
* @return {number} | ||
*/ | ||
status() { | ||
return this._status; | ||
} | ||
/** | ||
* @return {!Object} | ||
*/ | ||
headers() { | ||
return this._headers; | ||
} | ||
/** | ||
* @return {!Promise<!Buffer>} | ||
@@ -454,0 +518,0 @@ */ |
@@ -190,3 +190,4 @@ /** | ||
async evaluateHandle(pageFunction, ...args) { | ||
return this.mainFrame().executionContext().evaluateHandle(pageFunction, ...args); | ||
const context = await this.mainFrame().executionContext(); | ||
return context.evaluateHandle(pageFunction, ...args); | ||
} | ||
@@ -199,3 +200,4 @@ | ||
async queryObjects(prototypeHandle) { | ||
return this.mainFrame().executionContext().queryObjects(prototypeHandle); | ||
const context = await this.mainFrame().executionContext(); | ||
return context.queryObjects(prototypeHandle); | ||
} | ||
@@ -232,2 +234,10 @@ | ||
/** | ||
* @param {string} expression | ||
* @return {!Promise<?Puppeteer.ElementHandle>} | ||
*/ | ||
async xpath(expression) { | ||
return this.mainFrame().xpath(expression); | ||
} | ||
/** | ||
* @param {!Array<string>} urls | ||
@@ -259,7 +269,16 @@ * @return {!Promise<!Array<Network.Cookie>>} | ||
async setCookie(...cookies) { | ||
const pageURL = this.url(); | ||
const startsWithHTTP = pageURL.startsWith('http'); | ||
const items = cookies.map(cookie => { | ||
const item = Object.assign({}, cookie); | ||
const pageURL = this.url(); | ||
if (!item.url && pageURL.startsWith('http')) | ||
item.url = this.url(); | ||
if (!item.url && startsWithHTTP) | ||
item.url = pageURL; | ||
console.assert( | ||
item.url !== 'about:blank', | ||
`Blank page can not have cookie "${item.name}"` | ||
); | ||
console.assert( | ||
!String.prototype.startsWith.call(item.url || '', 'data:'), | ||
`Data URL page can not have cookie "${item.name}"` | ||
); | ||
return item; | ||
@@ -433,10 +452,3 @@ }); | ||
async content() { | ||
return await this.evaluate(() => { | ||
let retVal = ''; | ||
if (document.doctype) | ||
retVal = new XMLSerializer().serializeToString(document.doctype); | ||
if (document.documentElement) | ||
retVal += document.documentElement.outerHTML; | ||
return retVal; | ||
}); | ||
return await this._frameManager.mainFrame().content(); | ||
} | ||
@@ -448,7 +460,3 @@ | ||
async setContent(html) { | ||
await this.evaluate(html => { | ||
document.open(); | ||
document.write(html); | ||
document.close(); | ||
}, html); | ||
await this._frameManager.mainFrame().setContent(html); | ||
} | ||
@@ -466,3 +474,3 @@ | ||
const eventListeners = [ | ||
helper.addEventListener(this._networkManager, NetworkManager.Events.Request, request => requests.set(request.url, request)) | ||
helper.addEventListener(this._networkManager, NetworkManager.Events.Request, request => requests.set(request.url(), request)) | ||
]; | ||
@@ -523,3 +531,3 @@ | ||
const responses = new Map(); | ||
const listener = helper.addEventListener(this._networkManager, NetworkManager.Events.Response, response => responses.set(response.url, response)); | ||
const listener = helper.addEventListener(this._networkManager, NetworkManager.Events.Response, response => responses.set(response.url(), response)); | ||
const error = await watcher.navigationPromise(); | ||
@@ -634,3 +642,8 @@ helper.removeEventListeners([listener]); | ||
let screenshotType = null; | ||
if (options.path) { | ||
// options.type takes precedence over inferring the type from options.path | ||
// because it may be a 0-length file with no extension created beforehand (i.e. as a temp file). | ||
if (options.type) { | ||
console.assert(options.type === 'png' || options.type === 'jpeg', 'Unknown options.type value: ' + options.type); | ||
screenshotType = options.type; | ||
} else if (options.path) { | ||
const mimeType = mime.lookup(options.path); | ||
@@ -643,7 +656,3 @@ if (mimeType === 'image/png') | ||
} | ||
if (options.type) { | ||
console.assert(!screenshotType || options.type === screenshotType, `Passed screenshot type '${options.type}' does not match the type inferred from the file path: '${screenshotType}'`); | ||
console.assert(options.type === 'png' || options.type === 'jpeg', 'Unknown options.type value: ' + options.type); | ||
screenshotType = options.type; | ||
} | ||
if (!screenshotType) | ||
@@ -715,2 +724,4 @@ screenshotType = 'png'; | ||
const displayHeaderFooter = !!options.displayHeaderFooter; | ||
const headerTemplate = options.headerTemplate || ''; | ||
const footerTemplate = options.footerTemplate || ''; | ||
const printBackground = !!options.printBackground; | ||
@@ -741,2 +752,4 @@ const landscape = !!options.landscape; | ||
displayHeaderFooter: displayHeaderFooter, | ||
headerTemplate: headerTemplate, | ||
footerTemplate: footerTemplate, | ||
printBackground: printBackground, | ||
@@ -992,6 +1005,27 @@ scale: scale, | ||
constructor(type, text, args) { | ||
this.type = type; | ||
this.text = text; | ||
this.args = args; | ||
this._type = type; | ||
this._text = text; | ||
this._args = args; | ||
} | ||
/** | ||
* @return {string} | ||
*/ | ||
type() { | ||
return this._type; | ||
} | ||
/** | ||
* @return {string} | ||
*/ | ||
text() { | ||
return this._text; | ||
} | ||
/** | ||
* @return {!Array<string>} | ||
*/ | ||
args() { | ||
return this._args; | ||
} | ||
} | ||
@@ -998,0 +1032,0 @@ |
@@ -42,2 +42,9 @@ /** | ||
} | ||
/** | ||
* @return {!Array<string>} | ||
*/ | ||
static defaultArgs() { | ||
return Launcher.defaultArgs(); | ||
} | ||
} | ||
@@ -44,0 +51,0 @@ |
@@ -40,3 +40,3 @@ /** | ||
const categoriesArray = [ | ||
const defaultCategories = [ | ||
'-*', 'devtools.timeline', 'v8.execute', 'disabled-by-default-devtools.timeline', | ||
@@ -47,2 +47,3 @@ 'disabled-by-default-devtools.timeline.frame', 'toplevel', | ||
]; | ||
const categoriesArray = options.categories || defaultCategories; | ||
@@ -49,0 +50,0 @@ if (options.screenshots) |
{ | ||
"name": "puppeteer", | ||
"version": "0.13.0", | ||
"version": "1.0.0-next.1514510476874", | ||
"description": "A high-level API to control headless Chrome over the DevTools Protocol", | ||
@@ -11,5 +11,5 @@ "main": "index.js", | ||
"scripts": { | ||
"unit": "jasmine test/test.js", | ||
"debug-unit": "cross-env DEBUG_TEST=true node --inspect-brk ./node_modules/jasmine/bin/jasmine.js test/test.js", | ||
"test-doclint": "jasmine utils/doclint/check_public_api/test/test.js && jasmine utils/doclint/preprocessor/test.js", | ||
"unit": "node test/test.js", | ||
"debug-unit": "node --inspect-brk test/test.js", | ||
"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", | ||
@@ -20,6 +20,8 @@ "install": "node install.js", | ||
"coverage": "cross-env COVERAGE=true npm run unit", | ||
"test-node6-transformer": "jasmine utils/node6-transform/test/test.js", | ||
"test-node6-transformer": "node utils/node6-transform/test/test.js", | ||
"build": "node utils/node6-transform/index.js", | ||
"unit-node6": "jasmine node6-test/test.js", | ||
"tsc": "tsc -p ." | ||
"unit-node6": "node node6/test/test.js", | ||
"tsc": "tsc -p .", | ||
"prepublishOnly": "npm run build", | ||
"apply-next-version": "node utils/apply_next_version.js" | ||
}, | ||
@@ -39,3 +41,3 @@ "author": "The Chromium Authors", | ||
"puppeteer": { | ||
"chromium_revision": "515411" | ||
"chromium_revision": "524617" | ||
}, | ||
@@ -53,3 +55,2 @@ "devDependencies": { | ||
"esprima": "^4.0.0", | ||
"jasmine": "^2.6.0", | ||
"markdown-toc": "^1.1.0", | ||
@@ -56,0 +57,0 @@ "minimist": "^1.2.0", |
@@ -7,3 +7,3 @@ # Puppeteer [![Linux Build Status](https://img.shields.io/travis/GoogleChrome/puppeteer/master.svg)](https://travis-ci.org/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) [![NPM puppeteer package](https://img.shields.io/npm/v/puppeteer.svg)](https://npmjs.org/package/puppeteer) | ||
> Puppeteer is a Node library which provides a high-level API to control [headless](https://developers.google.com/web/updates/2017/04/headless-chrome) Chrome over the [DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/). It can also be configured to use full (non-headless) Chrome. | ||
> Puppeteer is a Node library which provides a high-level API to control [headless](https://developers.google.com/web/updates/2017/04/headless-chrome) Chrome or Chromium over the [DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/). It can also be configured to use full (non-headless) Chrome or Chromium. | ||
@@ -116,3 +116,3 @@ ###### What can I do? | ||
By default, Puppeteer downloads and uses a specific version of Chromium so its API | ||
is guaranteed to work out of the box. To use Puppeteer with a different version of Chrome, | ||
is guaranteed to work out of the box. To use Puppeteer with a different version of Chrome or Chromium, | ||
pass in the executable's path when creating a `Browser` instance: | ||
@@ -126,2 +126,5 @@ | ||
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/+/lkcr/docs/chromium_browser_vs_google_chrome.md) describes some differences for Linux users. | ||
**3. Creates a fresh user profile** | ||
@@ -139,3 +142,3 @@ | ||
displaying. Instead of launching in headless mode, launch a full version of | ||
Chrome using `headless: false`: | ||
the browser using `headless: false`: | ||
@@ -201,5 +204,5 @@ ```js | ||
Puppeteer works only with Chrome. However, many teams only run unit tests with a single browser (e.g. PhantomJS). In non-testing use cases, Puppeteer provides a powerful but simple API because it's only targeting one browser that enables you to rapidly develop automation scripts. | ||
Puppeteer works only with Chromium or Chrome. However, many teams only run unit tests with a single browser (e.g. PhantomJS). In non-testing use cases, Puppeteer provides a powerful but simple API because it's only targeting one browser that enables you to rapidly develop automation scripts. | ||
Puppeteer uses the latest versions of Chromium. | ||
Puppeteer bundles the latest versions of Chromium. | ||
@@ -206,0 +209,0 @@ #### Q: Who maintains Puppeteer? |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
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
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
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
Network access
Supply chain riskThis module accesses the network.
Found 2 instances in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Mixed license
License(Experimental) Package contains multiple licenses.
Found 1 instance in 1 package
18
0
225
24
6
240214
44
6025