Skip to content

Commit

Permalink
feat(types): add types for page.$$eval (#6139)
Browse files Browse the repository at this point in the history
* feat(types): add types for `page.$$eval`

* Add new-docs for $$eval

* fix example

* linting
  • Loading branch information
jackfranklin committed Jul 3, 2020
1 parent f7857d2 commit 5049b83
Show file tree
Hide file tree
Showing 8 changed files with 119 additions and 46 deletions.
6 changes: 3 additions & 3 deletions new-docs/puppeteer.elementhandle.__eval.md
Expand Up @@ -11,20 +11,20 @@ If `pageFunction` returns a Promise, then `frame.$$eval` would wait for the prom
<b>Signature:</b>

```typescript
$$eval<ReturnType extends any>(selector: string, pageFunction: EvaluateFn | string, ...args: SerializableOrJSHandle[]): Promise<ReturnType>;
$$eval<ReturnType>(selector: string, pageFunction: (elements: Element[], ...args: unknown[]) => ReturnType | Promise<ReturnType>, ...args: SerializableOrJSHandle[]): Promise<WrapElementHandle<ReturnType>>;
```

## Parameters

| Parameter | Type | Description |
| --- | --- | --- |
| selector | string | |
| pageFunction | [EvaluateFn](./puppeteer.evaluatefn.md) \| string | |
| pageFunction | (elements: Element\[\], ...args: unknown\[\]) =&gt; ReturnType \| Promise&lt;ReturnType&gt; | |
| args | [SerializableOrJSHandle](./puppeteer.serializableorjshandle.md)<!-- -->\[\] | |

<b>Returns:</b>

Promise&lt;ReturnType&gt;
Promise&lt;[WrapElementHandle](./puppeteer.wrapelementhandle.md)<!-- -->&lt;ReturnType&gt;&gt;

## Example 1

Expand Down
6 changes: 3 additions & 3 deletions new-docs/puppeteer.frame.__eval.md
Expand Up @@ -7,18 +7,18 @@
<b>Signature:</b>

```typescript
$$eval<ReturnType extends any>(selector: string, pageFunction: EvaluateFn | string, ...args: SerializableOrJSHandle[]): Promise<ReturnType>;
$$eval<ReturnType>(selector: string, pageFunction: (elements: Element[], ...args: unknown[]) => ReturnType | Promise<ReturnType>, ...args: SerializableOrJSHandle[]): Promise<WrapElementHandle<ReturnType>>;
```

## Parameters

| Parameter | Type | Description |
| --- | --- | --- |
| selector | string | |
| pageFunction | [EvaluateFn](./puppeteer.evaluatefn.md) \| string | |
| pageFunction | (elements: Element\[\], ...args: unknown\[\]) =&gt; ReturnType \| Promise&lt;ReturnType&gt; | |
| args | [SerializableOrJSHandle](./puppeteer.serializableorjshandle.md)<!-- -->\[\] | |

<b>Returns:</b>

Promise&lt;ReturnType&gt;
Promise&lt;[WrapElementHandle](./puppeteer.wrapelementhandle.md)<!-- -->&lt;ReturnType&gt;&gt;

39 changes: 31 additions & 8 deletions new-docs/puppeteer.page.__eval.md
Expand Up @@ -9,22 +9,22 @@ This method runs `Array.from(document.querySelectorAll(selector))` within the pa
<b>Signature:</b>

```typescript
$$eval<ReturnType extends any>(selector: string, pageFunction: EvaluateFn | string, ...args: SerializableOrJSHandle[]): Promise<ReturnType>;
$$eval<ReturnType>(selector: string, pageFunction: (elements: Element[], ...args: unknown[]) => ReturnType | Promise<ReturnType>, ...args: SerializableOrJSHandle[]): Promise<WrapElementHandle<ReturnType>>;
```

## Parameters

| Parameter | Type | Description |
| --- | --- | --- |
| selector | string | the [selector](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors) to query for |
| pageFunction | [EvaluateFn](./puppeteer.evaluatefn.md) \| string | the function to be evaluated in the page context. Will be passed the result of <code>Array.from(document.querySelectorAll(selector))</code> as its first argument. |
| pageFunction | (elements: Element\[\], ...args: unknown\[\]) =&gt; ReturnType \| Promise&lt;ReturnType&gt; | the function to be evaluated in the page context. Will be passed the result of <code>Array.from(document.querySelectorAll(selector))</code> as its first argument. |
| args | [SerializableOrJSHandle](./puppeteer.serializableorjshandle.md)<!-- -->\[\] | any additional arguments to pass through to <code>pageFunction</code>. |

<b>Returns:</b>

Promise&lt;ReturnType&gt;
Promise&lt;[WrapElementHandle](./puppeteer.wrapelementhandle.md)<!-- -->&lt;ReturnType&gt;&gt;

The result of calling `pageFunction`<!-- -->.
The result of calling `pageFunction`<!-- -->. If it returns an element it is wrapped in an [ElementHandle](./puppeteer.elementhandle.md)<!-- -->, else the raw value itself is returned.

## Remarks

Expand All @@ -33,17 +33,40 @@ If `pageFunction` returns a promise `$$eval` will wait for the promise to resolv
## Example 1


```js
```
// get the amount of divs on the page
const divCount = await page.$$eval('div', divs => divs.length);
// get the text content of all the `.options` elements:
const options = await page.$$eval('div > span.options', options => {
return options.map(option => option.textContent)
});
```
If you are using TypeScript, you may have to provide an explicit type to the first argument of the `pageFunction`<!-- -->. By default it is typed as `Element[]`<!-- -->, but you may need to provide a more specific sub-type:

## Example 2


```js
const options = await page.$$eval(
'div > span.options', options => options.map(option => option.textContent));
```
// if you don't provide HTMLInputElement here, TS will error
// as `value` is not on `Element`
await page.$$eval('input', (elements: HTMLInputElement[]) => {
return elements.map(e => e.value);
});
```
The compiler should be able to infer the return type from the `pageFunction` you provide. If it is unable to, you can use the generic type to tell the compiler what return type you expect from `$$eval`<!-- -->:

## Example 3


```
// The compiler can infer the return type in this case, but if it can't
// or if you want to be more explicit, provide it as the generic type.
const allInputValues = await page.$$eval<string[]>(
'input', (elements: HTMLInputElement[]) => elements.map(e => e.textContent)
);
```

10 changes: 6 additions & 4 deletions src/common/DOMWorld.ts
Expand Up @@ -25,7 +25,6 @@ import { MouseButton } from './Input';
import { FrameManager, Frame } from './FrameManager';
import { getQueryHandlerAndSelector, QueryHandler } from './QueryHandler';
import {
EvaluateFn,
SerializableOrJSHandle,
EvaluateHandleFn,
WrapElementHandle,
Expand Down Expand Up @@ -172,11 +171,14 @@ export class DOMWorld {
return document.$eval<ReturnType>(selector, pageFunction, ...args);
}

async $$eval<ReturnType extends any>(
async $$eval<ReturnType>(
selector: string,
pageFunction: EvaluateFn | string,
pageFunction: (
elements: Element[],
...args: unknown[]
) => ReturnType | Promise<ReturnType>,
...args: SerializableOrJSHandle[]
): Promise<ReturnType> {
): Promise<WrapElementHandle<ReturnType>> {
const document = await this._document();
const value = await document.$$eval<ReturnType>(
selector,
Expand Down
10 changes: 6 additions & 4 deletions src/common/FrameManager.ts
Expand Up @@ -30,7 +30,6 @@ import { Page } from './Page';
import { HTTPResponse } from './HTTPResponse';
import Protocol from '../protocol';
import {
EvaluateFn,
SerializableOrJSHandle,
EvaluateHandleFn,
WrapElementHandle,
Expand Down Expand Up @@ -496,11 +495,14 @@ export class Frame {
return this._mainWorld.$eval<ReturnType>(selector, pageFunction, ...args);
}

async $$eval<ReturnType extends any>(
async $$eval<ReturnType>(
selector: string,
pageFunction: EvaluateFn | string,
pageFunction: (
elements: Element[],
...args: unknown[]
) => ReturnType | Promise<ReturnType>,
...args: SerializableOrJSHandle[]
): Promise<ReturnType> {
): Promise<WrapElementHandle<ReturnType>> {
return this._mainWorld.$$eval<ReturnType>(selector, pageFunction, ...args);
}

Expand Down
24 changes: 16 additions & 8 deletions src/common/JSHandle.ts
Expand Up @@ -882,11 +882,14 @@ export class ElementHandle<
* .toEqual(['Hello!', 'Hi!']);
* ```
*/
async $$eval<ReturnType extends any>(
async $$eval<ReturnType>(
selector: string,
pageFunction: EvaluateFn | string,
pageFunction: (
elements: Element[],
...args: unknown[]
) => ReturnType | Promise<ReturnType>,
...args: SerializableOrJSHandle[]
): Promise<ReturnType> {
): Promise<WrapElementHandle<ReturnType>> {
const defaultHandler = (element: Element, selector: string) =>
Array.from(element.querySelectorAll(selector));
const { updatedSelector, queryHandler } = getQueryHandlerAndSelector(
Expand All @@ -898,12 +901,17 @@ export class ElementHandle<
queryHandler,
updatedSelector
);
const result = await arrayHandle.evaluate<(...args: any[]) => ReturnType>(
pageFunction,
...args
);
const result = await arrayHandle.evaluate<
(
elements: Element[],
...args: unknown[]
) => ReturnType | Promise<ReturnType>
>(pageFunction, ...args);
await arrayHandle.dispose();
return result;
/* This as exists for the same reason as the `as` in $eval above.
* See the comment there for a ful explanation.
*/
return result as WrapElementHandle<ReturnType>;
}

/**
Expand Down
66 changes: 52 additions & 14 deletions src/common/Page.ts
Expand Up @@ -43,7 +43,6 @@ import { ConsoleMessage, ConsoleMessageType } from './ConsoleMessage';
import { PuppeteerLifeCycleEvent } from './LifecycleWatcher';
import Protocol from '../protocol';
import {
EvaluateFn,
SerializableOrJSHandle,
EvaluateHandleFn,
WrapElementHandle,
Expand Down Expand Up @@ -795,31 +794,70 @@ export class Page extends EventEmitter {
* resolve and then return its value.
*
* @example
* ```js
*
* ```
* // get the amount of divs on the page
* const divCount = await page.$$eval('div', divs => divs.length);
*
* // get the text content of all the `.options` elements:
* const options = await page.$$eval('div > span.options', options => {
* return options.map(option => option.textContent)
* });
* ```
*
* If you are using TypeScript, you may have to provide an explicit type to the
* first argument of the `pageFunction`.
* By default it is typed as `Element[]`, but you may need to provide a more
* specific sub-type:
*
* @example
* ```js
* const options = await page.$$eval(
* 'div > span.options', options => options.map(option => option.textContent));
*
* ```
* // if you don't provide HTMLInputElement here, TS will error
* // as `value` is not on `Element`
* await page.$$eval('input', (elements: HTMLInputElement[]) => {
* return elements.map(e => e.value);
* });
* ```
*
* @param selector - the
* The compiler should be able to infer the return type
* from the `pageFunction` you provide. If it is unable to, you can use the generic
* type to tell the compiler what return type you expect from `$$eval`:
*
* @example
*
* ```
* // The compiler can infer the return type in this case, but if it can't
* // or if you want to be more explicit, provide it as the generic type.
* const allInputValues = await page.$$eval<string[]>(
* 'input', (elements: HTMLInputElement[]) => elements.map(e => e.textContent)
* );
* ```
*
* @param selector the
* {@link https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors | selector}
* to query for
* @param pageFunction - the function to be evaluated in the page context.
* Will be passed the result of
* `Array.from(document.querySelectorAll(selector))` as its first argument.
* @param args - any additional arguments to pass through to `pageFunction`.
* @param pageFunction the function to be evaluated in the page context. Will
* be passed the result of `Array.from(document.querySelectorAll(selector))`
* as its first argument.
* @param args any additional arguments to pass through to `pageFunction`.
*
* @returns The result of calling `pageFunction`.
* @returns The result of calling `pageFunction`. If it returns an element it
* is wrapped in an {@link ElementHandle}, else the raw value itself is
* returned.
*/
async $$eval<ReturnType extends any>(
async $$eval<ReturnType>(
selector: string,
pageFunction: EvaluateFn | string,
pageFunction: (
elements: Element[],
/* These have to be typed as unknown[] for the same reason as the $eval
* definition above, please see that comment for more details and the TODO
* that will improve things.
*/
...args: unknown[]
) => ReturnType | Promise<ReturnType>,
...args: SerializableOrJSHandle[]
): Promise<ReturnType> {
): Promise<WrapElementHandle<ReturnType>> {
return this.mainFrame().$$eval<ReturnType>(selector, pageFunction, ...args);
}

Expand Down
4 changes: 2 additions & 2 deletions test/queryselector.spec.ts
Expand Up @@ -218,7 +218,7 @@ describe('querySelector', function () {
'<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) =>
const content = await tweet.$$eval('.like', (nodes: HTMLElement[]) =>
nodes.map((n) => n.innerText)
);
expect(content).toEqual(['100', '10']);
Expand All @@ -231,7 +231,7 @@ describe('querySelector', function () {
'<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) =>
const content = await elementHandle.$$eval('.a', (nodes: HTMLElement[]) =>
nodes.map((n) => n.innerText)
);
expect(content).toEqual(['a1-child-div', 'a2-child-div']);
Expand Down

0 comments on commit 5049b83

Please sign in to comment.