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

* feat(types): add types for `page.$$eval`

* Add new-docs for $$eval

* fix example

* linting
This commit is contained in:
Jack Franklin 2020-07-03 15:23:51 +01:00 committed by GitHub
parent f7857d27c4
commit 5049b83186
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 120 additions and 47 deletions

View File

@ -11,7 +11,7 @@ If `pageFunction` returns a Promise, then `frame.$$eval` would wait for the prom
<b>Signature:</b> <b>Signature:</b>
```typescript ```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 ## Parameters
@ -19,12 +19,12 @@ $$eval<ReturnType extends any>(selector: string, pageFunction: EvaluateFn | stri
| Parameter | Type | Description | | Parameter | Type | Description |
| --- | --- | --- | | --- | --- | --- |
| selector | string | | | selector | string | |
| pageFunction | [EvaluateFn](./puppeteer.evaluatefn.md) \| string | | | pageFunction | (elements: Element\[\], ...args: unknown\[\]) =&gt; ReturnType \| Promise&lt;ReturnType&gt; | |
| args | [SerializableOrJSHandle](./puppeteer.serializableorjshandle.md)<!-- -->\[\] | | | args | [SerializableOrJSHandle](./puppeteer.serializableorjshandle.md)<!-- -->\[\] | |
<b>Returns:</b> <b>Returns:</b>
Promise&lt;ReturnType&gt; Promise&lt;[WrapElementHandle](./puppeteer.wrapelementhandle.md)<!-- -->&lt;ReturnType&gt;&gt;
## Example 1 ## Example 1

View File

@ -7,7 +7,7 @@
<b>Signature:</b> <b>Signature:</b>
```typescript ```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 ## Parameters
@ -15,10 +15,10 @@ $$eval<ReturnType extends any>(selector: string, pageFunction: EvaluateFn | stri
| Parameter | Type | Description | | Parameter | Type | Description |
| --- | --- | --- | | --- | --- | --- |
| selector | string | | | selector | string | |
| pageFunction | [EvaluateFn](./puppeteer.evaluatefn.md) \| string | | | pageFunction | (elements: Element\[\], ...args: unknown\[\]) =&gt; ReturnType \| Promise&lt;ReturnType&gt; | |
| args | [SerializableOrJSHandle](./puppeteer.serializableorjshandle.md)<!-- -->\[\] | | | args | [SerializableOrJSHandle](./puppeteer.serializableorjshandle.md)<!-- -->\[\] | |
<b>Returns:</b> <b>Returns:</b>
Promise&lt;ReturnType&gt; Promise&lt;[WrapElementHandle](./puppeteer.wrapelementhandle.md)<!-- -->&lt;ReturnType&gt;&gt;

View File

@ -9,7 +9,7 @@ This method runs `Array.from(document.querySelectorAll(selector))` within the pa
<b>Signature:</b> <b>Signature:</b>
```typescript ```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 ## Parameters
@ -17,14 +17,14 @@ $$eval<ReturnType extends any>(selector: string, pageFunction: EvaluateFn | stri
| Parameter | Type | Description | | Parameter | Type | Description |
| --- | --- | --- | | --- | --- | --- |
| selector | string | the [selector](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors) to query for | | 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>. | | args | [SerializableOrJSHandle](./puppeteer.serializableorjshandle.md)<!-- -->\[\] | any additional arguments to pass through to <code>pageFunction</code>. |
<b>Returns:</b> <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 ## Remarks
@ -33,17 +33,40 @@ If `pageFunction` returns a promise `$$eval` will wait for the promise to resolv
## Example 1 ## Example 1
```js ```
// get the amount of divs on the page
const divCount = await page.$$eval('div', divs => divs.length); 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 ## Example 2
```js ```
const options = await page.$$eval( // if you don't provide HTMLInputElement here, TS will error
'div > span.options', options => options.map(option => option.textContent)); // 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)
);
``` ```

View File

@ -25,7 +25,6 @@ import { MouseButton } from './Input';
import { FrameManager, Frame } from './FrameManager'; import { FrameManager, Frame } from './FrameManager';
import { getQueryHandlerAndSelector, QueryHandler } from './QueryHandler'; import { getQueryHandlerAndSelector, QueryHandler } from './QueryHandler';
import { import {
EvaluateFn,
SerializableOrJSHandle, SerializableOrJSHandle,
EvaluateHandleFn, EvaluateHandleFn,
WrapElementHandle, WrapElementHandle,
@ -172,11 +171,14 @@ export class DOMWorld {
return document.$eval<ReturnType>(selector, pageFunction, ...args); return document.$eval<ReturnType>(selector, pageFunction, ...args);
} }
async $$eval<ReturnType extends any>( async $$eval<ReturnType>(
selector: string, selector: string,
pageFunction: EvaluateFn | string, pageFunction: (
elements: Element[],
...args: unknown[]
) => ReturnType | Promise<ReturnType>,
...args: SerializableOrJSHandle[] ...args: SerializableOrJSHandle[]
): Promise<ReturnType> { ): Promise<WrapElementHandle<ReturnType>> {
const document = await this._document(); const document = await this._document();
const value = await document.$$eval<ReturnType>( const value = await document.$$eval<ReturnType>(
selector, selector,

View File

@ -30,7 +30,6 @@ import { Page } from './Page';
import { HTTPResponse } from './HTTPResponse'; import { HTTPResponse } from './HTTPResponse';
import Protocol from '../protocol'; import Protocol from '../protocol';
import { import {
EvaluateFn,
SerializableOrJSHandle, SerializableOrJSHandle,
EvaluateHandleFn, EvaluateHandleFn,
WrapElementHandle, WrapElementHandle,
@ -496,11 +495,14 @@ export class Frame {
return this._mainWorld.$eval<ReturnType>(selector, pageFunction, ...args); return this._mainWorld.$eval<ReturnType>(selector, pageFunction, ...args);
} }
async $$eval<ReturnType extends any>( async $$eval<ReturnType>(
selector: string, selector: string,
pageFunction: EvaluateFn | string, pageFunction: (
elements: Element[],
...args: unknown[]
) => ReturnType | Promise<ReturnType>,
...args: SerializableOrJSHandle[] ...args: SerializableOrJSHandle[]
): Promise<ReturnType> { ): Promise<WrapElementHandle<ReturnType>> {
return this._mainWorld.$$eval<ReturnType>(selector, pageFunction, ...args); return this._mainWorld.$$eval<ReturnType>(selector, pageFunction, ...args);
} }

View File

@ -882,11 +882,14 @@ export class ElementHandle<
* .toEqual(['Hello!', 'Hi!']); * .toEqual(['Hello!', 'Hi!']);
* ``` * ```
*/ */
async $$eval<ReturnType extends any>( async $$eval<ReturnType>(
selector: string, selector: string,
pageFunction: EvaluateFn | string, pageFunction: (
elements: Element[],
...args: unknown[]
) => ReturnType | Promise<ReturnType>,
...args: SerializableOrJSHandle[] ...args: SerializableOrJSHandle[]
): Promise<ReturnType> { ): Promise<WrapElementHandle<ReturnType>> {
const defaultHandler = (element: Element, selector: string) => const defaultHandler = (element: Element, selector: string) =>
Array.from(element.querySelectorAll(selector)); Array.from(element.querySelectorAll(selector));
const { updatedSelector, queryHandler } = getQueryHandlerAndSelector( const { updatedSelector, queryHandler } = getQueryHandlerAndSelector(
@ -898,12 +901,17 @@ export class ElementHandle<
queryHandler, queryHandler,
updatedSelector updatedSelector
); );
const result = await arrayHandle.evaluate<(...args: any[]) => ReturnType>( const result = await arrayHandle.evaluate<
pageFunction, (
...args elements: Element[],
); ...args: unknown[]
) => ReturnType | Promise<ReturnType>
>(pageFunction, ...args);
await arrayHandle.dispose(); 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>;
} }
/** /**

View File

@ -43,7 +43,6 @@ import { ConsoleMessage, ConsoleMessageType } from './ConsoleMessage';
import { PuppeteerLifeCycleEvent } from './LifecycleWatcher'; import { PuppeteerLifeCycleEvent } from './LifecycleWatcher';
import Protocol from '../protocol'; import Protocol from '../protocol';
import { import {
EvaluateFn,
SerializableOrJSHandle, SerializableOrJSHandle,
EvaluateHandleFn, EvaluateHandleFn,
WrapElementHandle, WrapElementHandle,
@ -795,31 +794,70 @@ export class Page extends EventEmitter {
* resolve and then return its value. * resolve and then return its value.
* *
* @example * @example
* ```js *
* const divCount = await page.$$eval('div', divs => divs.length);
* ``` * ```
* // 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 * @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} * {@link https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors | selector}
* to query for * to query for
* @param pageFunction - the function to be evaluated in the page context. * @param pageFunction the function to be evaluated in the page context. Will
* Will be passed the result of * be passed the result of `Array.from(document.querySelectorAll(selector))`
* `Array.from(document.querySelectorAll(selector))` as its first argument. * as its first argument.
* @param args - any additional arguments to pass through to `pageFunction`. * @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, 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[] ...args: SerializableOrJSHandle[]
): Promise<ReturnType> { ): Promise<WrapElementHandle<ReturnType>> {
return this.mainFrame().$$eval<ReturnType>(selector, pageFunction, ...args); return this.mainFrame().$$eval<ReturnType>(selector, pageFunction, ...args);
} }

View File

@ -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>' '<html><body><div class="tweet"><div class="like">100</div><div class="like">10</div></div></body></html>'
); );
const tweet = await page.$('.tweet'); 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) nodes.map((n) => n.innerText)
); );
expect(content).toEqual(['100', '10']); expect(content).toEqual(['100', '10']);
@ -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>'; '<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); await page.setContent(htmlContent);
const elementHandle = await page.$('#myId'); 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) nodes.map((n) => n.innerText)
); );
expect(content).toEqual(['a1-child-div', 'a2-child-div']); expect(content).toEqual(['a1-child-div', 'a2-child-div']);