From 7a8aa73466494e1f03be926cda86e719376bf36d Mon Sep 17 00:00:00 2001 From: Andrey Lushnikov Date: Tue, 10 Oct 2017 23:23:14 -0700 Subject: [PATCH] feat(Page): introduce Page.$$eval method (#1006) This patch adds a `Page.$$eval` method that runs `document.querySelectorAll` and passes resulting array to the page function. Fixes #625. --- docs/api.md | 33 +++++++++++++++++++++++++++++++++ lib/FrameManager.js | 16 ++++++++++++++-- lib/Page.js | 10 ++++++++++ test/test.js | 8 ++++++++ 4 files changed, 65 insertions(+), 2 deletions(-) diff --git a/docs/api.md b/docs/api.md index be40e88f..0f781227 100644 --- a/docs/api.md +++ b/docs/api.md @@ -33,6 +33,7 @@ + [event: 'response'](#event-response) + [page.$(selector)](#pageselector) + [page.$$(selector)](#pageselector) + + [page.$$eval(selector, pageFunction[, ...args])](#pageevalselector-pagefunction-args) + [page.$eval(selector, pageFunction[, ...args])](#pageevalselector-pagefunction-args) + [page.addScriptTag(url)](#pageaddscripttagurl) + [page.addStyleTag(url)](#pageaddstyletagurl) @@ -110,6 +111,7 @@ * [class: Frame](#class-frame) + [frame.$(selector)](#frameselector) + [frame.$$(selector)](#frameselector) + + [frame.$$eval(selector, pageFunction[, ...args])](#frameevalselector-pagefunction-args) + [frame.$eval(selector, pageFunction[, ...args])](#frameevalselector-pagefunction-args) + [frame.addScriptTag(url)](#frameaddscripttagurl) + [frame.addStyleTag(url)](#frameaddstyletagurl) @@ -379,6 +381,22 @@ The method runs `document.querySelectorAll` within the page. If no elements matc Shortcut for [page.mainFrame().$$(selector)](#frameselector-1). + +#### page.$$eval(selector, pageFunction[, ...args]) +- `selector` <[string]> A [selector] to query frame for +- `pageFunction` <[function]> Function to be evaluated in browser context +- `...args` <...[Serializable]|[ElementHandle]> Arguments to pass to `pageFunction` +- returns: <[Promise]<[Serializable]>> Promise which resolves to the return value of `pageFunction` + +This method runs `document.querySelectorAll` within the page and passes it as the first argument to `pageFunction`. + +If `pageFunction` returns a [Promise], then `page.$$eval` would wait for the promise to resolve and return its value. + +Examples: +```js +const divsCounts = await page.$$eval('div', divs => divs.length); +``` + #### page.$eval(selector, pageFunction[, ...args]) - `selector` <[string]> A [selector] to query page for - `pageFunction` <[function]> Function to be evaluated in browser context @@ -1260,6 +1278,21 @@ The method queries frame for the selector. If there's no such element within the The method runs `document.querySelectorAll` within the frame. If no elements match the selector, the return value resolve to `[]`. +#### frame.$$eval(selector, pageFunction[, ...args]) +- `selector` <[string]> A [selector] to query frame for +- `pageFunction` <[function]> Function to be evaluated in browser context +- `...args` <...[Serializable]|[ElementHandle]> Arguments to pass to `pageFunction` +- returns: <[Promise]<[Serializable]>> Promise which resolves to the return value of `pageFunction` + +This method runs `document.querySelectorAll` within the frame and passes it as the first argument to `pageFunction`. + +If `pageFunction` returns a [Promise], then `frame.$$eval` would wait for the promise to resolve and return its value. + +Examples: +```js +const divsCounts = await frame.$$eval('div', divs => divs.length); +``` + #### frame.$eval(selector, pageFunction[, ...args]) - `selector` <[string]> A [selector] to query frame for - `pageFunction` <[function]> Function to be evaluated in browser context diff --git a/lib/FrameManager.js b/lib/FrameManager.js index 0b03e54c..b61e30d1 100644 --- a/lib/FrameManager.js +++ b/lib/FrameManager.js @@ -232,12 +232,24 @@ class Frame { const elementHandle = await this.$(selector); if (!elementHandle) throw new Error(`Error: failed to find element matching selector "${selector}"`); - args = [elementHandle].concat(args); - const result = await this.evaluate(pageFunction, ...args); + const result = await this.evaluate(pageFunction, elementHandle, ...args); await elementHandle.dispose(); 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._context.evaluateHandle(selector => Array.from(document.querySelectorAll(selector)), selector); + const result = await this.evaluate(pageFunction, arrayHandle, ...args); + await arrayHandle.dispose(); + return result; + } + /** * @param {string} selector * @return {!Promise>} diff --git a/lib/Page.js b/lib/Page.js index 72cd627a..5ecfedd7 100644 --- a/lib/Page.js +++ b/lib/Page.js @@ -186,6 +186,16 @@ class Page extends EventEmitter { return this.mainFrame().$eval(selector, pageFunction, ...args); } + /** + * @param {string} selector + * @param {Function|string} pageFunction + * @param {!Array<*>} args + * @return {!Promise<(!Object|undefined)>} + */ + async $$eval(selector, pageFunction, ...args) { + return this.mainFrame().$$eval(selector, pageFunction, ...args); + } + /** * @param {string} selector * @return {!Promise>} diff --git a/test/test.js b/test/test.js index 0a763d30..62bfe734 100644 --- a/test/test.js +++ b/test/test.js @@ -1392,6 +1392,14 @@ describe('Page', function() { })); }); + describe('Page.$$eval', function() { + it('should work', SX(async function() { + await page.setContent('
hello
beautiful
world!
'); + const divsCount = await page.$$eval('div', divs => divs.length); + expect(divsCount).toBe(3); + })); + }); + describe('Page.$', function() { it('should query existing element', SX(async function() { await page.setContent('
test
');