feat(elementhandle): add elementHandle.$$eval method (#2589)

Fixes #2401
This commit is contained in:
Vasyl Pahut 2018-05-26 02:56:51 +03:00 committed by Andrey Lushnikov
parent 32f4c173c8
commit 1e07925e26
4 changed files with 69 additions and 5 deletions

View File

@ -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
<div class="feed">
<div class="tweet">Hello!</div>
<div class="tweet">Hi!</div>
</div>
```
```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

View File

@ -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<!Array<!ElementHandle>>}

View File

@ -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;
}
/**

View File

@ -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('<html><body><div class="tweet"><div class="like">100</div><div class="like">10</div></div></body></html>');
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 = '<div class="a">not-a-child-div</div><div id="myId"><div class="a">a1-child-div</div><div class="a">a2-child-div</div></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 = '<div class="a">not-a-child-div</div><div id="myId"></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}) => {