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 $eval #6135

Merged
merged 1 commit into from Jul 2, 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 promi
<b>Signature:</b>

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

## Parameters

| Parameter | Type | Description |
| --- | --- | --- |
| selector | string | |
| pageFunction | [EvaluateFn](./puppeteer.evaluatefn.md) \| string | |
| pageFunction | (element: 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

Expand Down
4 changes: 2 additions & 2 deletions new-docs/puppeteer.elementhandle.aselement.md
Expand Up @@ -7,9 +7,9 @@
<b>Signature:</b>

```typescript
asElement(): ElementHandle | null;
asElement(): ElementHandle<ElementType> | null;
```
<b>Returns:</b>

[ElementHandle](./puppeteer.elementhandle.md) \| null
[ElementHandle](./puppeteer.elementhandle.md)<!-- -->&lt;ElementType&gt; \| null

4 changes: 3 additions & 1 deletion new-docs/puppeteer.elementhandle.md
Expand Up @@ -9,7 +9,7 @@ ElementHandle represents an in-page DOM element.
<b>Signature:</b>

```typescript
export declare class ElementHandle extends JSHandle
export declare class ElementHandle<ElementType extends Element = Element> extends JSHandle
```
<b>Extends:</b> [JSHandle](./puppeteer.jshandle.md)

Expand All @@ -34,6 +34,8 @@ ElementHandle prevents the DOM element from being garbage-collected unless the h

ElementHandle instances can be used as arguments in [Page.$eval()](./puppeteer.page._eval.md) and [Page.evaluate()](./puppeteer.page.evaluate.md) methods.

If you're using TypeScript, ElementHandle takes a generic argument that denotes the type of element the handle is holding within. For example, if you have a handle to a `<select>` element, you can type it as `ElementHandle<HTMLSelectElement>` and you get some nicer type checks.

The constructor for this class is marked as internal. Third-party code should not call the constructor directly or create subclasses that extend the `ElementHandle` class.

## Methods
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: (element: Element, ...args: unknown[]) => ReturnType | Promise<ReturnType>, ...args: SerializableOrJSHandle[]): Promise<WrapElementHandle<ReturnType>>;
```

## Parameters

| Parameter | Type | Description |
| --- | --- | --- |
| selector | string | |
| pageFunction | [EvaluateFn](./puppeteer.evaluatefn.md) \| string | |
| pageFunction | (element: 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;

2 changes: 1 addition & 1 deletion new-docs/puppeteer.jshandle.evaluatehandle.md
Expand Up @@ -9,7 +9,7 @@ This method passes this handle as the first argument to `pageFunction`<!-- -->.
<b>Signature:</b>

```typescript
evaluateHandle<HandleType extends JSHandle | ElementHandle = JSHandle>(pageFunction: EvaluateHandleFn, ...args: SerializableOrJSHandle[]): Promise<HandleType>;
evaluateHandle<HandleType extends JSHandle = JSHandle>(pageFunction: EvaluateHandleFn, ...args: SerializableOrJSHandle[]): Promise<HandleType>;
```

## Parameters
Expand Down
2 changes: 2 additions & 0 deletions new-docs/puppeteer.md
Expand Up @@ -89,4 +89,6 @@
| [PuppeteerErrors](./puppeteer.puppeteererrors.md) | |
| [Serializable](./puppeteer.serializable.md) | |
| [SerializableOrJSHandle](./puppeteer.serializableorjshandle.md) | |
| [UnwrapElementHandle](./puppeteer.unwrapelementhandle.md) | Unwraps a DOM element out of an ElementHandle instance |
| [WrapElementHandle](./puppeteer.wrapelementhandle.md) | Wraps a DOM element into an ElementHandle instance |

54 changes: 49 additions & 5 deletions new-docs/puppeteer.page._eval.md
Expand Up @@ -4,21 +4,65 @@

## Page.$eval() method

This method runs `document.querySelector` within the page and passes the result as the first argument to the `pageFunction`<!-- -->.

<b>Signature:</b>

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

## Parameters

| Parameter | Type | Description |
| --- | --- | --- |
| selector | string | |
| pageFunction | [EvaluateFn](./puppeteer.evaluatefn.md) \| string | |
| args | [SerializableOrJSHandle](./puppeteer.serializableorjshandle.md)<!-- -->\[\] | |
| selector | string | the [selector](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors) to query for |
| pageFunction | (element: 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>document.querySelector(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`<!-- -->. If it returns an element it is wrapped in an [ElementHandle](./puppeteer.elementhandle.md)<!-- -->, else the raw value itself is returned.

## Remarks

If no element is found matching `selector`<!-- -->, the method will throw an error.

If `pageFunction` returns a promise `$eval` will wait for the promise to resolve and then return its value.

## Example 1


```
const searchValue = await page.$eval('#search', el => el.value);
const preloadHref = await page.$eval('link[rel=preload]', el => el.href);
const html = await page.$eval('.main-container', el => el.outerHTML);

```
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


```
// if you don't provide HTMLInputElement here, TS will error
// as `value` is not on `Element`
const searchValue = await page.$eval('#search', (el: HTMLInputElement) => el.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 searchValue = await page.$eval<string>(
'#search', (el: HTMLInputElement) => el.value
);

```

2 changes: 1 addition & 1 deletion new-docs/puppeteer.page.md
Expand Up @@ -73,7 +73,7 @@ page.off('request', logRequest);
| [$(selector)](./puppeteer.page._.md) | | Runs <code>document.querySelector</code> within the page. If no element matches the selector, the return value resolves to <code>null</code>. |
| [$$(selector)](./puppeteer.page.__.md) | | |
| [$$eval(selector, pageFunction, args)](./puppeteer.page.__eval.md) | | |
| [$eval(selector, pageFunction, args)](./puppeteer.page._eval.md) | | |
| [$eval(selector, pageFunction, args)](./puppeteer.page._eval.md) | | This method runs <code>document.querySelector</code> within the page and passes the result as the first argument to the <code>pageFunction</code>. |
| [$x(expression)](./puppeteer.page._x.md) | | |
| [addScriptTag(options)](./puppeteer.page.addscripttag.md) | | |
| [addStyleTag(options)](./puppeteer.page.addstyletag.md) | | |
Expand Down
13 changes: 13 additions & 0 deletions new-docs/puppeteer.unwrapelementhandle.md
@@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [puppeteer](./puppeteer.md) &gt; [UnwrapElementHandle](./puppeteer.unwrapelementhandle.md)

## UnwrapElementHandle type

Unwraps a DOM element out of an ElementHandle instance

<b>Signature:</b>

```typescript
export declare type UnwrapElementHandle<X> = X extends ElementHandle<infer E> ? E : X;
```
13 changes: 13 additions & 0 deletions new-docs/puppeteer.wrapelementhandle.md
@@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [puppeteer](./puppeteer.md) &gt; [WrapElementHandle](./puppeteer.wrapelementhandle.md)

## WrapElementHandle type

Wraps a DOM element into an ElementHandle instance

<b>Signature:</b>

```typescript
export declare type WrapElementHandle<X> = X extends Element ? ElementHandle<X> : X;
```
10 changes: 7 additions & 3 deletions src/common/DOMWorld.ts
Expand Up @@ -28,6 +28,7 @@ import {
EvaluateFn,
SerializableOrJSHandle,
EvaluateHandleFn,
WrapElementHandle,
} from './EvalTypes';
import { isNode } from '../environment';

Expand Down Expand Up @@ -153,11 +154,14 @@ export class DOMWorld {
return value;
}

async $eval<ReturnType extends any>(
async $eval<ReturnType>(
selector: string,
pageFunction: EvaluateFn | string,
pageFunction: (
element: Element,
...args: unknown[]
) => ReturnType | Promise<ReturnType>,
...args: SerializableOrJSHandle[]
): Promise<ReturnType> {
): Promise<WrapElementHandle<ReturnType>> {
const document = await this._document();
return document.$eval<ReturnType>(selector, pageFunction, ...args);
}
Expand Down
14 changes: 13 additions & 1 deletion src/common/EvalTypes.ts
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

import { JSHandle } from './JSHandle';
import { JSHandle, ElementHandle } from './JSHandle';

/**
* @public
Expand Down Expand Up @@ -61,3 +61,15 @@ export interface JSONObject {
* @public
*/
export type SerializableOrJSHandle = Serializable | JSHandle;

/**
* Wraps a DOM element into an ElementHandle instance
* @public
**/
export type WrapElementHandle<X> = X extends Element ? ElementHandle<X> : X;

/**
* Unwraps a DOM element out of an ElementHandle instance
* @public
**/
export type UnwrapElementHandle<X> = X extends ElementHandle<infer E> ? E : X;
10 changes: 7 additions & 3 deletions src/common/FrameManager.ts
Expand Up @@ -33,6 +33,7 @@ import {
EvaluateFn,
SerializableOrJSHandle,
EvaluateHandleFn,
WrapElementHandle,
} from './EvalTypes';

const UTILITY_WORLD_NAME = '__puppeteer_utility_world__';
Expand Down Expand Up @@ -457,11 +458,14 @@ export class Frame {
return this._mainWorld.$x(expression);
}

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

Expand Down
56 changes: 38 additions & 18 deletions src/common/JSHandle.ts
Expand Up @@ -28,6 +28,7 @@ import {
SerializableOrJSHandle,
EvaluateFnReturnType,
EvaluateHandleFn,
WrapElementHandle,
} from './EvalTypes';

export interface BoxModel {
Expand Down Expand Up @@ -175,7 +176,7 @@ export class JSHandle {
*
* See {@link Page.evaluateHandle} for more details.
*/
async evaluateHandle<HandleType extends JSHandle | ElementHandle = JSHandle>(
async evaluateHandle<HandleType extends JSHandle = JSHandle>(
pageFunction: EvaluateHandleFn,
...args: SerializableOrJSHandle[]
): Promise<HandleType> {
Expand Down Expand Up @@ -316,9 +317,16 @@ export class JSHandle {
* ElementHandle instances can be used as arguments in {@link Page.$eval} and
* {@link Page.evaluate} methods.
*
* If you're using TypeScript, ElementHandle takes a generic argument that
* denotes the type of element the handle is holding within. For example, if you
* have a handle to a `<select>` element, you can type it as
* `ElementHandle<HTMLSelectElement>` and you get some nicer type checks.
*
* @public
*/
export class ElementHandle extends JSHandle {
export class ElementHandle<
ElementType extends Element = Element
> extends JSHandle {
private _page: Page;
private _frameManager: FrameManager;

Expand All @@ -339,7 +347,7 @@ export class ElementHandle extends JSHandle {
this._frameManager = frameManager;
}

asElement(): ElementHandle | null {
asElement(): ElementHandle<ElementType> | null {
return this;
}

Expand All @@ -358,7 +366,7 @@ export class ElementHandle extends JSHandle {
private async _scrollIntoViewIfNeeded(): Promise<void> {
const error = await this.evaluate<
(
element: HTMLElement,
element: Element,
pageJavascriptEnabled: boolean
) => Promise<string | false>
>(async (element, pageJavascriptEnabled) => {
Expand Down Expand Up @@ -512,11 +520,9 @@ export class ElementHandle extends JSHandle {
'"'
);

/* TODO(jacktfranklin@): once ExecutionContext is TypeScript, and
* its evaluate function is properly typed with generics we can
* return here and remove the typecasting
*/
return this.evaluate((element: HTMLSelectElement, values: string[]) => {
return this.evaluate<
(element: HTMLSelectElement, values: string[]) => string[]
>((element, values) => {
if (element.nodeName.toLowerCase() !== 'select')
throw new Error('Element is not a <select> element.');

Expand Down Expand Up @@ -582,7 +588,7 @@ export class ElementHandle extends JSHandle {
// not actually update the files in that case, so the solution is to eval the element
// value to a new FileList directly.
if (files.length === 0) {
await this.evaluate((element: HTMLInputElement) => {
await this.evaluate<(element: HTMLInputElement) => void>((element) => {
element.files = new DataTransfer().files;

// Dispatch events for this case because it should behave akin to a user action.
Expand Down Expand Up @@ -821,22 +827,36 @@ export class ElementHandle extends JSHandle {
* expect(await tweetHandle.$eval('.retweets', node => node.innerText)).toBe('10');
* ```
*/
async $eval<ReturnType extends any>(
async $eval<ReturnType>(
selector: string,
pageFunction: EvaluateFn | string,
pageFunction: (
element: Element,
...args: unknown[]
) => ReturnType | Promise<ReturnType>,
...args: SerializableOrJSHandle[]
): Promise<ReturnType> {
): Promise<WrapElementHandle<ReturnType>> {
const elementHandle = await this.$(selector);
if (!elementHandle)
throw new Error(
`Error: failed to find element matching selector "${selector}"`
);
const result = await elementHandle.evaluate<(...args: any[]) => ReturnType>(
pageFunction,
...args
);
const result = await elementHandle.evaluate<
(
element: Element,
...args: SerializableOrJSHandle[]
) => ReturnType | Promise<ReturnType>
>(pageFunction, ...args);
await elementHandle.dispose();
return result;

/**
* This as is a little unfortunate but helps TS understand the behavour of
* `elementHandle.evaluate`. If evalute returns an element it will return an
* ElementHandle instance, rather than the plain object. All the
* WrapElementHandle type does is wrap ReturnType into
* ElementHandle<ReturnType> if it is an ElementHandle, or leave it alone as
* ReturnType if it isn't.
*/
return result as WrapElementHandle<ReturnType>;
}

/**
Expand Down