From 88b996877fd83514cc5c2bedee7b817db9a0eb79 Mon Sep 17 00:00:00 2001 From: "Denny Ku(kuni)" Date: Wed, 9 May 2018 10:17:59 +0900 Subject: [PATCH] feat(ElementHandle): introduce elementHandle.$eval (#2407) This patch introduces `elementHandle.$eval` method. References #2401. --- docs/api.md | 18 ++++++++++++++++++ lib/ElementHandle.js | 15 +++++++++++++++ lib/FrameManager.js | 9 +++------ test/elementhandle.spec.js | 16 ++++++++++++++++ 4 files changed, 52 insertions(+), 6 deletions(-) diff --git a/docs/api.md b/docs/api.md index 9e42d1933b0..32e36019d2f 100644 --- a/docs/api.md +++ b/docs/api.md @@ -189,6 +189,7 @@ - [class: ElementHandle](#class-elementhandle) * [elementHandle.$(selector)](#elementhandleselector) * [elementHandle.$$(selector)](#elementhandleselector) + * [elementHandle.$eval(selector, pageFunction, ...args)](#elementhandleevalselector-pagefunction-args) * [elementHandle.$x(expression)](#elementhandlexexpression) * [elementHandle.asElement()](#elementhandleaselement) * [elementHandle.boundingBox()](#elementhandleboundingbox) @@ -2230,6 +2231,23 @@ The method runs `element.querySelector` within the page. If no element matches t The method runs `element.querySelectorAll` within the page. If no elements match the selector, the return value resolve to `[]`. +#### elementHandle.$eval(selector, pageFunction, ...args) +- `selector` <[string]> A [selector] to query page for +- `pageFunction` <[function]> Function to be evaluated in browser context +- `...args` <...[Serializable]|[JSHandle]> Arguments to pass to `pageFunction` +- returns: <[Promise]<[Serializable]>> Promise which resolves to the return value of `pageFunction` + +This method runs `document.querySelector` within the element and passes it as the first argument to `pageFunction`. If there's no element matching `selector`, the method throws an error. + +If `pageFunction` returns a [Promise], then `frame.$eval` would wait for the promise to resolve and return its value. + +Examples: +```js +const tweetHandle = await page.$('.tweet'); +expect(await tweetHandle.$eval('.like', node => node.innerText)).toBe('100'); +expect(await tweetHandle.$eval('.retweets', node => node.innerText)).toBe('10'); +``` + #### elementHandle.$x(expression) - `expression` <[string]> Expression to [evaluate](https://developer.mozilla.org/en-US/docs/Web/API/Document/evaluate). - returns: <[Promise]> Promise which resolves to ElementHandle pointing to the frame element. diff --git a/lib/ElementHandle.js b/lib/ElementHandle.js index e34ad58100a..c5bec74c547 100644 --- a/lib/ElementHandle.js +++ b/lib/ElementHandle.js @@ -280,6 +280,21 @@ class ElementHandle extends JSHandle { return result; } + /** + * @param {string} selector + * @param {Function|String} pageFunction + * @param {!Array<*>} args + * @return {!Promise<(!Object|undefined)>} + */ + async $eval(selector, pageFunction, ...args) { + const elementHandle = await this.$(selector); + if (!elementHandle) + throw new Error(`Error: failed to find element matching selector "${selector}"`); + const result = await this.executionContext().evaluate(pageFunction, elementHandle, ...args); + await elementHandle.dispose(); + return result; + } + /** * @param {string} expression * @return {!Promise>} diff --git a/lib/FrameManager.js b/lib/FrameManager.js index b2e3f965dce..e01c7aff45e 100644 --- a/lib/FrameManager.js +++ b/lib/FrameManager.js @@ -366,12 +366,9 @@ class Frame { * @return {!Promise<(!Object|undefined)>} */ async $eval(selector, pageFunction, ...args) { - const elementHandle = await this.$(selector); - if (!elementHandle) - throw new Error(`Error: failed to find element matching selector "${selector}"`); - const result = await this.evaluate(pageFunction, elementHandle, ...args); - await elementHandle.dispose(); - return result; + const document = await this._document(); + const value = await document.$eval(selector, pageFunction, ...args); + return value; } /** diff --git a/test/elementhandle.spec.js b/test/elementhandle.spec.js index 44cdee3909c..9489c943e6e 100644 --- a/test/elementhandle.spec.js +++ b/test/elementhandle.spec.js @@ -294,6 +294,22 @@ module.exports.addTests = function({testRunner, expect}) { expect(second).toBe(null); }); }); + describe('ElementHandle.$eval', function() { + it('should work', async({page, server}) => { + await page.setContent('
10
'); + const tweet = await page.$('.tweet'); + const content = await tweet.$eval('.like', node => node.innerText); + expect(content).toBe('100'); + }); + + it('should retrieve content from subtree', async({page, server}) => { + const htmlContent = '
not-a-child-div
a-child-div
'; + await page.setContent(htmlContent); + const elementHandle = await page.$('#myId'); + const content = await elementHandle.$eval('.a', node => node.innerText); + expect(content).toBe('a-child-div'); + }); + }); describe('ElementHandle.$$', function() { it('should query existing elements', async({page, server}) => {