From bbde8fd1c2d8d0b7b43cac6701f196f02d3bdcda Mon Sep 17 00:00:00 2001 From: JoelEinbinder Date: Thu, 27 Jul 2017 12:23:41 -0700 Subject: [PATCH] page.evaluate takes a string in addition to function (#135) This patch improves on page.evaluate to accept a string. The string can have a trailing '//# sourceURL=' comment which would name the evaluation to make stacks beautiful. In order to make sourceURL comments possible, this patch: - removes wrapping of the client function into `Promise.resolve()` - stops passing `awaitPromise` parameter to `Runtime.evaluate` - starts to await promise via the `Runtime.awaitPromise` if the return type of the evaluation is promise closes #118 --- docs/api.md | 40 ++++++++++++++++++++++++---------------- lib/FrameManager.js | 24 +++++++----------------- lib/Page.js | 2 +- lib/helper.js | 16 +++++++++++++++- phantom_shim/WebPage.js | 4 +++- test/test.js | 12 ++++++++++++ 6 files changed, 62 insertions(+), 36 deletions(-) diff --git a/docs/api.md b/docs/api.md index 4aa0c70af01..a6ced9cf3e1 100644 --- a/docs/api.md +++ b/docs/api.md @@ -354,14 +354,34 @@ Adds a `` tag to the page with the desired url. Alternatively, - returns: <[Promise]> Returns promise which resolves when page gets closed. #### page.evaluate(pageFunction, ...args) -- `pageFunction` <[function]> Function to be evaluated in browser context +- `pageFunction` <[function]|[string]> Function to be evaluated in browser context - `...args` <...[string]> Arguments to pass to `pageFunction` - returns: <[Promise]<[Object]>> Promise which resolves to function return value +If the function, passed to the `page.evaluate`, returns a [Promise], then `page.evaluate` would wait for the promise to resolve and return it's value. + +```js +const {Browser} = require('puppeteer'); +const browser = new Browser(); +browser.newPage().then(async page => + const result = await page.evaluate(() => { + return Promise.resolve().then(() => 8 * 7); + }); + console.log(result); // prints "56" + browser.close(); +}); +``` + +A string can also be passed in instead of a function. + +```js +console.log(await page.evaluate('1 + 2')); // prints "3" +``` + This is a shortcut for [page.mainFrame().evaluate()](#frameevaluatefun-args) method. #### page.evaluateOnNewDocument(pageFunction, ...args) -- `pageFunction` <[function]> Function to be evaluated in browser context +- `pageFunction` <[function]|[string]> Function to be evaluated in browser context - `...args` <...[string]> Arguments to pass to `pageFunction` - returns: <[Promise]<[Object]>> Promise which resolves to function @@ -825,23 +845,11 @@ Adds a `` tag to the frame with the desired url. Alternatively, - returns: <[Promise]> Promise which resolves when the element matching `selector` is successfully clicked. Promise gets rejected if there's no element matching `selector`. #### frame.evaluate(pageFunction, ...args) -- `pageFunction` <[function]> Function to be evaluated in browser context +- `pageFunction` <[function]|[string]> Function to be evaluated in browser context - `...args` <...[string]> Arguments to pass to `pageFunction` - returns: <[Promise]<[Object]>> Promise which resolves to function return value -If the function, passed to the `page.evaluate`, returns a [Promise], then `page.evaluate` would wait for the promise to resolve and return it's value. - -```js -const {Browser} = require('puppeteer'); -const browser = new Browser(); -browser.newPage().then(async page => - const result = await page.evaluate(() => { - return Promise.resolve().then(() => 8 * 7); - }); - console.log(result); // prints "56" - browser.close(); -}); -``` +If the function, passed to the `frame.evaluate`, returns a [Promise], then `frame.evaluate` would wait for the promise to resolve and return it's value. #### frame.focus(selector) - `selector` <[string]> A query selector of element to focus. If there are multiple elements satisfying the selector, the first will be focused. diff --git a/lib/FrameManager.js b/lib/FrameManager.js index 9873aa79e25..13f7b017ed2 100644 --- a/lib/FrameManager.js +++ b/lib/FrameManager.js @@ -187,24 +187,14 @@ class Frame { } /** - * @param {function()} pageFunction + * @param {function()|string} pageFunction * @param {!Array<*>} args * @return {!Promise<(!Object|undefined)>} */ async evaluate(pageFunction, ...args) { - return this._evaluateExpression(helper.evaluationString(pageFunction, ...args), true); - } - - /** - * @param {string} expression - * @param {boolean} awaitPromise - * @return {!Promise<(!Object|undefined)>} - */ - async _evaluateExpression(expression, awaitPromise) { + let expression = helper.evaluationString(pageFunction, ...args); const contextId = this._defaultContextId; - if (awaitPromise) - expression = `Promise.resolve(${expression})`; - let { exceptionDetails, result: remoteObject } = await this._client.send('Runtime.evaluate', { expression, contextId, returnByValue: false, awaitPromise}); + let { exceptionDetails, result: remoteObject } = await this._client.send('Runtime.evaluate', { expression, contextId, returnByValue: false}); if (exceptionDetails) throw new Error('Evaluation failed: ' + helper.getExceptionMessage(exceptionDetails)); return await helper.serializeRemoteObject(this._client, remoteObject); @@ -257,7 +247,7 @@ class Frame { }); }); contents += `//# sourceURL=` + filePath.replace(/\n/g,''); - return this._evaluateExpression(contents, false); + return this.evaluate(contents); } /** @@ -319,7 +309,7 @@ class Frame { return null; return (${pageFunction})(${argsString}); })()`; - return this._evaluateExpression(expression, true); + return this.evaluate(expression); } /** @@ -335,7 +325,7 @@ class Frame { let nodes = document.querySelectorAll(${JSON.stringify(selector)}); return Array.prototype.map.call(nodes, (node, index) => (${pageFunction})(${argsString})); })()`; - return this._evaluateExpression(expression, true); + return this.evaluate(expression); } /** @@ -457,7 +447,7 @@ class WaitTask { let success = false; let error = null; try { - success = await this._frame._evaluateExpression(this._pageScript, true); + success = await this._frame.evaluate(this._pageScript); } catch (e) { error = e; } diff --git a/lib/Page.js b/lib/Page.js index 6a4895efba5..8a172640618 100644 --- a/lib/Page.js +++ b/lib/Page.js @@ -345,7 +345,7 @@ class Page extends EventEmitter { } /** - * @param {function()} pageFunction + * @param {function()|string} pageFunction * @param {!Array<*>} args * @return {!Promise} */ diff --git a/lib/helper.js b/lib/helper.js index d077dcece3e..248ec465c55 100644 --- a/lib/helper.js +++ b/lib/helper.js @@ -16,11 +16,15 @@ class Helper { /** - * @param {function()} fun + * @param {function()|string} fun * @param {!Array<*>} args * @return {string} */ static evaluationString(fun, ...args) { + if (typeof fun === 'string') { + console.assert(args.length === 0, 'Cannot evaluate a string with arguments'); + return fun; + } return `(${fun})(${args.map(x => JSON.stringify(x)).join(',')})`; } @@ -48,6 +52,16 @@ class Helper { * @return {!Promise} */ static async serializeRemoteObject(client, remoteObject) { + if (remoteObject.subtype === 'promise') { + let response = (await client.send('Runtime.awaitPromise', { + promiseObjectId: remoteObject.objectId, + returnByValue: false + })); + Helper.releaseObject(client, remoteObject); + if (response.exceptionDetails) + throw new Error('Evaluation failed: ' + Helper.getExceptionMessage(response.exceptionDetails)); + remoteObject = response.result; + } if (remoteObject.unserializableValue) { switch (remoteObject.unserializableValue) { case '-0': diff --git a/phantom_shim/WebPage.js b/phantom_shim/WebPage.js index 84583b49f94..076611edca9 100644 --- a/phantom_shim/WebPage.js +++ b/phantom_shim/WebPage.js @@ -533,10 +533,12 @@ class WebPage { } /** - * @param {function()} fun + * @param {function()|string} fun * @param {!Array} args */ evaluate(fun, ...args) { + if (typeof fun === 'string') + fun = `(${fun})()`; if (this._deferEvaluate) return await(this._page.evaluateOnNewDocument(fun, ...args)); return await(this._currentFrame.evaluate(fun, ...args)); diff --git a/test/test.js b/test/test.js index 372d5150e0b..f8414c4f779 100644 --- a/test/test.js +++ b/test/test.js @@ -144,6 +144,18 @@ describe('Puppeteer', function() { let result = await page.evaluate(() => window); expect(result).toBe('Window'); })); + it('should accept a string', SX(async function() { + let result = await page.evaluate('1 + 2'); + expect(result).toBe(3); + })); + it('should accept a string with semi colons', SX(async function() { + let result = await page.evaluate('1 + 5;'); + expect(result).toBe(6); + })); + it('should accept a string with comments', SX(async function() { + let result = await page.evaluate('2 + 5;\n// do some math!'); + expect(result).toBe(7); + })); }); describe('Page.injectFile', function() {