Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(types): add types for page.$$eval #6139

Merged
merged 4 commits into from Jul 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will API Extractor report a warning here if we remove the - after pageFunction?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, it needs there to be a - between the param name and the description

* 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