diff --git a/docs/api.md b/docs/api.md
index 4d06575ddc6..ee5826ef8a8 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -205,6 +205,7 @@
- [class: ElementHandle](#class-elementhandle)
* [elementHandle.$(selector)](#elementhandleselector)
* [elementHandle.$$(selector)](#elementhandleselector)
+ * [elementHandle.$$eval(selector, pageFunction, ...args)](#elementhandleevalselector-pagefunction-args)
* [elementHandle.$eval(selector, pageFunction, ...args)](#elementhandleevalselector-pagefunction-args)
* [elementHandle.$x(expression)](#elementhandlexexpression)
* [elementHandle.asElement()](#elementhandleaselement)
@@ -2397,6 +2398,28 @@ 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.querySelectorAll` 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:
+```html
+
+
+
+
+```
+```js
+const feedHandle = await page.$('.feed');
+expect(await feedHandle.$$eval('.tweet', nodes => nodes.map(n => n.innerText)).toEqual(['Hello!', 'Hi!']);
+```
+
#### elementHandle.$eval(selector, pageFunction, ...args)
- `selector` <[string]> A [selector] to query page for
- `pageFunction` <[function]> Function to be evaluated in browser context
diff --git a/lib/ElementHandle.js b/lib/ElementHandle.js
index c5bec74c547..5bba6af281b 100644
--- a/lib/ElementHandle.js
+++ b/lib/ElementHandle.js
@@ -295,6 +295,25 @@ 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 arrayHandle = await this.executionContext().evaluateHandle(
+ (element, selector) => Array.from(element.querySelectorAll(selector)),
+ this, selector
+ );
+ if (!(await arrayHandle.jsonValue()).length)
+ throw new Error(`Error: failed to find elements matching selector "${selector}"`);
+
+ const result = await this.executionContext().evaluate(pageFunction, arrayHandle, ...args);
+ await arrayHandle.dispose();
+ return result;
+ }
+
/**
* @param {string} expression
* @return {!Promise>}
diff --git a/lib/FrameManager.js b/lib/FrameManager.js
index 8ea3b8fce8f..65d43e163cb 100644
--- a/lib/FrameManager.js
+++ b/lib/FrameManager.js
@@ -378,11 +378,9 @@ class Frame {
* @return {!Promise<(!Object|undefined)>}
*/
async $$eval(selector, pageFunction, ...args) {
- 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);
- await arrayHandle.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 e44ef8a1c17..111afd7363e 100644
--- a/test/elementhandle.spec.js
+++ b/test/elementhandle.spec.js
@@ -318,6 +318,30 @@ module.exports.addTests = function({testRunner, expect}) {
expect(errorMessage).toBe(`Error: failed to find element matching selector ".a"`);
});
});
+ describe('ElementHandle.$$eval', function() {
+ it('should work', async({page, server}) => {
+ await page.setContent('');
+ const tweet = await page.$('.tweet');
+ const content = await tweet.$$eval('.like', nodes => nodes.map(n => n.innerText));
+ expect(content).toEqual(['100', '10']);
+ });
+
+ it('should retrieve content from subtree', async({page, server}) => {
+ const htmlContent = 'not-a-child-div
';
+ await page.setContent(htmlContent);
+ const elementHandle = await page.$('#myId');
+ const content = await elementHandle.$$eval('.a', nodes => nodes.map(n => n.innerText));
+ expect(content).toEqual(['a1-child-div', 'a2-child-div']);
+ });
+
+ it('should throw in case of missing selector', async({page, server}) => {
+ const htmlContent = 'not-a-child-div
';
+ await page.setContent(htmlContent);
+ const elementHandle = await page.$('#myId');
+ const errorMessage = await elementHandle.$$eval('.a', nodes => nodes.map(n => n.innerText)).catch(error => error.message);
+ expect(errorMessage).toBe(`Error: failed to find elements matching selector ".a"`);
+ });
+ });
describe('ElementHandle.$$', function() {
it('should query existing elements', async({page, server}) => {