feat(types): add (and fix) evaluateHandle types (#6130)

This change started as a small change to pull types from DefinitelyTyped over to
Puppeteer for the `evaluateHandle` function but instead ended up also fixing
what looks to be a long standing issue with our existing documentation.

`evaluateHandle` can in fact return an `ElementHandle` rather than a `JSHandle`.
Note that `ElementHandle` extends `JSHandle` so whilst the docs are technically
correct (all ElementHandles are JSHandles) it's confusing because JSHandles
don't have methods like `click` on them, but ElementHandles do.

if you return something that is an HTML element:

```
const button = page.evaluateHandle(() => document.querySelector('button'));
// this is an ElementHandle, not a JSHandle
```

Therefore I've updated the original docs and added a large explanation to the
TSDoc for `page.evaluateHandle`.

In TypeScript land we'll assume the function will return a `JSHandle` but you
can tell TS otherwise via the generic argument, which can only be `JSHandle`
(the default) or `ElementHandle`:

```
const button = page.evaluateHandle<ElementHandle>(() => document.querySelector('button'));
```
This commit is contained in:
Jack Franklin 2020-07-01 12:44:08 +01:00 committed by GitHub
parent 3c0dc45e47
commit 8370ec88ae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 235 additions and 97 deletions

View File

@ -1456,7 +1456,7 @@ Shortcut for [page.mainFrame().evaluate(pageFunction, ...args)](#frameevaluatepa
#### page.evaluateHandle(pageFunction[, ...args])
- `pageFunction` <[function]|[string]> Function to be evaluated in the page context
- `...args` <...[Serializable]|[JSHandle]> Arguments to pass to `pageFunction`
- returns: <[Promise]<[JSHandle]>> Promise which resolves to the return value of `pageFunction` as in-page object (JSHandle)
- returns: <[Promise]<[JSHandle]|[ElementHandle]>> Promise which resolves to the return value of `pageFunction` as an in-page object.
The only difference between `page.evaluate` and `page.evaluateHandle` is that `page.evaluateHandle` returns in-page object (JSHandle).
@ -1475,6 +1475,14 @@ console.log(await resultHandle.jsonValue());
await resultHandle.dispose();
```
This function will return a [JSHandle] by default, however if your `pageFunction` returns an HTML element you will get back an `ElementHandle`:
```js
const button = await page.evaluateHandle(() => document.querySelector('button'))
// button is an ElementHandle, so you can call methods such as click:
await button.click();
```
Shortcut for [page.mainFrame().executionContext().evaluateHandle(pageFunction, ...args)](#executioncontextevaluatehandlepagefunction-args).
#### page.evaluateOnNewDocument(pageFunction[, ...args])
@ -2298,12 +2306,14 @@ Shortcut for [(await worker.executionContext()).evaluate(pageFunction, ...args)]
#### webWorker.evaluateHandle(pageFunction[, ...args])
- `pageFunction` <[function]|[string]> Function to be evaluated in the page context
- `...args` <...[Serializable]|[JSHandle]> Arguments to pass to `pageFunction`
- returns: <[Promise]<[JSHandle]>> Promise which resolves to the return value of `pageFunction` as in-page object (JSHandle)
- returns: <[Promise]<[JSHandle]|[ElementHandle]>> Promise which resolves to the return value of `pageFunction` as an in-page object.
The only difference between `worker.evaluate` and `worker.evaluateHandle` is that `worker.evaluateHandle` returns in-page object (JSHandle).
If the function passed to the `worker.evaluateHandle` returns a [Promise], then `worker.evaluateHandle` would wait for the promise to resolve and return its value.
If the function returns an element, the returned handle is an [ElementHandle].
Shortcut for [(await worker.executionContext()).evaluateHandle(pageFunction, ...args)](#executioncontextevaluatehandlepagefunction-args).
#### webWorker.executionContext()
@ -2855,12 +2865,14 @@ await bodyHandle.dispose();
#### frame.evaluateHandle(pageFunction[, ...args])
- `pageFunction` <[function]|[string]> Function to be evaluated in the page context
- `...args` <...[Serializable]|[JSHandle]> Arguments to pass to `pageFunction`
- returns: <[Promise]<[JSHandle]>> Promise which resolves to the return value of `pageFunction` as in-page object (JSHandle)
- returns: <[Promise]<[JSHandle]|[ElementHandle]>> Promise which resolves to the return value of `pageFunction` as an in-page object.
The only difference between `frame.evaluate` and `frame.evaluateHandle` is that `frame.evaluateHandle` returns in-page object (JSHandle).
If the function, passed to the `frame.evaluateHandle`, returns a [Promise], then `frame.evaluateHandle` would wait for the promise to resolve and return its value.
If the function returns an element, the returned handle is an [ElementHandle].
```js
const aWindowHandle = await frame.evaluateHandle(() => Promise.resolve(window));
aWindowHandle; // Handle for the window object.
@ -3184,10 +3196,12 @@ console.log(result); // prints '3'.
#### executionContext.evaluateHandle(pageFunction[, ...args])
- `pageFunction` <[function]|[string]> Function to be evaluated in the `executionContext`
- `...args` <...[Serializable]|[JSHandle]> Arguments to pass to `pageFunction`
- returns: <[Promise]<[JSHandle]>> Promise which resolves to the return value of `pageFunction` as in-page object (JSHandle)
- returns: <[Promise]<[JSHandle]|[ElementHandle]>> Promise which resolves to the return value of `pageFunction` as an in-page object.
The only difference between `executionContext.evaluate` and `executionContext.evaluateHandle` is that `executionContext.evaluateHandle` returns in-page object (JSHandle).
If the function returns an element, the returned handle is an [ElementHandle].
If the function passed to the `executionContext.evaluateHandle` returns a [Promise], then `executionContext.evaluateHandle` would wait for the promise to resolve and return its value.
```js
@ -3277,12 +3291,14 @@ expect(await tweetHandle.evaluate(node => node.innerText)).toBe('10');
#### jsHandle.evaluateHandle(pageFunction[, ...args])
- `pageFunction` <[function]|[string]> Function to be evaluated
- `...args` <...[Serializable]|[JSHandle]> Arguments to pass to `pageFunction`
- returns: <[Promise]<[JSHandle]>> Promise which resolves to the return value of `pageFunction` as in-page object (JSHandle)
- returns: <[Promise]<[JSHandle]|[ElementHandle]>> Promise which resolves to the return value of `pageFunction` as an in-page object.
This method passes this handle as the first argument to `pageFunction`.
The only difference between `jsHandle.evaluate` and `jsHandle.evaluateHandle` is that `executionContext.evaluateHandle` returns in-page object (JSHandle).
If the function returns an element, the returned handle is an [ElementHandle].
If the function passed to the `jsHandle.evaluateHandle` returns a [Promise], then `jsHandle.evaluateHandle` would wait for the promise to resolve and return its value.
See [Page.evaluateHandle](#pageevaluatehandlepagefunction-args) for more details.
@ -3466,12 +3482,14 @@ expect(await tweetHandle.evaluate(node => node.innerText)).toBe('10');
#### elementHandle.evaluateHandle(pageFunction[, ...args])
- `pageFunction` <[function]|[string]> Function to be evaluated
- `...args` <...[Serializable]|[JSHandle]> Arguments to pass to `pageFunction`
- returns: <[Promise]<[JSHandle]>> Promise which resolves to the return value of `pageFunction` as in-page object (JSHandle)
- returns: <[Promise]<[JSHandle]|[ElementHandle]>> Promise which resolves to the return value of `pageFunction` as an in-page object.
This method passes this handle as the first argument to `pageFunction`.
The only difference between `evaluateHandle.evaluate` and `evaluateHandle.evaluateHandle` is that `executionContext.evaluateHandle` returns in-page object (JSHandle).
If the function returns an element, the returned handle is an [ElementHandle].
If the function passed to the `evaluateHandle.evaluateHandle` returns a [Promise], then `evaluateHandle.evaluateHandle` would wait for the promise to resolve and return its value.
See [Page.evaluateHandle](#pageevaluatehandlepagefunction-args) for more details.

View File

@ -0,0 +1,12 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [puppeteer](./puppeteer.md) &gt; [EvaluateHandleFn](./puppeteer.evaluatehandlefn.md)
## EvaluateHandleFn type
<b>Signature:</b>
```typescript
export declare type EvaluateHandleFn = string | ((...args: unknown[]) => unknown);
```

View File

@ -7,19 +7,19 @@
<b>Signature:</b>
```typescript
evaluateHandle(pageFunction: Function | string, ...args: unknown[]): Promise<JSHandle>;
evaluateHandle<HandleType extends JSHandle | ElementHandle = JSHandle>(pageFunction: EvaluateHandleFn, ...args: SerializableOrJSHandle[]): Promise<HandleType>;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| pageFunction | Function \| string | a function to be evaluated in the <code>executionContext</code> |
| args | unknown\[\] | argument to pass to the page function |
| pageFunction | [EvaluateHandleFn](./puppeteer.evaluatehandlefn.md) | a function to be evaluated in the <code>executionContext</code> |
| args | [SerializableOrJSHandle](./puppeteer.serializableorjshandle.md)<!-- -->\[\] | argument to pass to the page function |
<b>Returns:</b>
Promise&lt;[JSHandle](./puppeteer.jshandle.md)<!-- -->&gt;
Promise&lt;HandleType&gt;
A promise that resolves to the return value of the given function as an in-page object (a [JSHandle](./puppeteer.jshandle.md)<!-- -->).

View File

@ -7,17 +7,17 @@
<b>Signature:</b>
```typescript
evaluateHandle(pageFunction: Function | string, ...args: unknown[]): Promise<JSHandle>;
evaluateHandle<HandlerType extends JSHandle = JSHandle>(pageFunction: EvaluateHandleFn, ...args: SerializableOrJSHandle[]): Promise<HandlerType>;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| pageFunction | Function \| string | |
| args | unknown\[\] | |
| pageFunction | [EvaluateHandleFn](./puppeteer.evaluatehandlefn.md) | |
| args | [SerializableOrJSHandle](./puppeteer.serializableorjshandle.md)<!-- -->\[\] | |
<b>Returns:</b>
Promise&lt;[JSHandle](./puppeteer.jshandle.md)<!-- -->&gt;
Promise&lt;HandlerType&gt;

View File

@ -7,7 +7,7 @@
<b>Signature:</b>
```typescript
waitFor(selectorOrFunctionOrTimeout: string | number | Function, options?: {}, ...args: unknown[]): Promise<JSHandle | null>;
waitFor(selectorOrFunctionOrTimeout: string | number | Function, options?: {}, ...args: SerializableOrJSHandle[]): Promise<JSHandle | null>;
```
## Parameters
@ -16,7 +16,7 @@ waitFor(selectorOrFunctionOrTimeout: string | number | Function, options?: {}, .
| --- | --- | --- |
| selectorOrFunctionOrTimeout | string \| number \| Function | |
| options | {} | |
| args | unknown\[\] | |
| args | [SerializableOrJSHandle](./puppeteer.serializableorjshandle.md)<!-- -->\[\] | |
<b>Returns:</b>

View File

@ -10,7 +10,7 @@
waitForFunction(pageFunction: Function | string, options?: {
polling?: string | number;
timeout?: number;
}, ...args: unknown[]): Promise<JSHandle>;
}, ...args: SerializableOrJSHandle[]): Promise<JSHandle>;
```
## Parameters
@ -19,7 +19,7 @@ waitForFunction(pageFunction: Function | string, options?: {
| --- | --- | --- |
| pageFunction | Function \| string | |
| options | { polling?: string \| number; timeout?: number; } | |
| args | unknown\[\] | |
| args | [SerializableOrJSHandle](./puppeteer.serializableorjshandle.md)<!-- -->\[\] | |
<b>Returns:</b>

View File

@ -9,19 +9,19 @@ This method passes this handle as the first argument to `pageFunction`<!-- -->.
<b>Signature:</b>
```typescript
evaluateHandle(pageFunction: Function | string, ...args: unknown[]): Promise<JSHandle>;
evaluateHandle<HandleType extends JSHandle | ElementHandle = JSHandle>(pageFunction: EvaluateHandleFn, ...args: SerializableOrJSHandle[]): Promise<HandleType>;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| pageFunction | Function \| string | |
| args | unknown\[\] | |
| pageFunction | [EvaluateHandleFn](./puppeteer.evaluatehandlefn.md) | |
| args | [SerializableOrJSHandle](./puppeteer.serializableorjshandle.md)<!-- -->\[\] | |
<b>Returns:</b>
Promise&lt;[JSHandle](./puppeteer.jshandle.md)<!-- -->&gt;
Promise&lt;HandleType&gt;
## Remarks

View File

@ -82,6 +82,7 @@
| [ConsoleMessageType](./puppeteer.consolemessagetype.md) | The supported types for console messages. |
| [EvaluateFn](./puppeteer.evaluatefn.md) | |
| [EvaluateFnReturnType](./puppeteer.evaluatefnreturntype.md) | |
| [EvaluateHandleFn](./puppeteer.evaluatehandlefn.md) | |
| [JSONArray](./puppeteer.jsonarray.md) | |
| [KeyInput](./puppeteer.keyinput.md) | |
| [MouseButtonInput](./puppeteer.mousebuttoninput.md) | |

View File

@ -7,17 +7,62 @@
<b>Signature:</b>
```typescript
evaluateHandle(pageFunction: Function | string, ...args: unknown[]): Promise<JSHandle>;
evaluateHandle<HandlerType extends JSHandle = JSHandle>(pageFunction: EvaluateHandleFn, ...args: SerializableOrJSHandle[]): Promise<HandlerType>;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| pageFunction | Function \| string | |
| args | unknown\[\] | |
| pageFunction | [EvaluateHandleFn](./puppeteer.evaluatehandlefn.md) | a function that is run within the page |
| args | [SerializableOrJSHandle](./puppeteer.serializableorjshandle.md)<!-- -->\[\] | arguments to be passed to the pageFunction |
<b>Returns:</b>
Promise&lt;[JSHandle](./puppeteer.jshandle.md)<!-- -->&gt;
Promise&lt;HandlerType&gt;
## Remarks
The only difference between [page.evaluate](./puppeteer.page.evaluate.md) and `page.evaluateHandle` is that `evaluateHandle` will return the value wrapped in an in-page object.
If the function passed to `page.evaluteHandle` returns a Promise, the function will wait for the promise to resolve and return its value.
You can pass a string instead of a function (although functions are recommended as they are easier to debug and use with TypeScript):
## Example 1
```
const aHandle = await page.evaluateHandle('document')
```
## Example 2
[JSHandle](./puppeteer.jshandle.md) instances can be passed as arguments to the `pageFunction`<!-- -->:
```
const aHandle = await page.evaluateHandle(() => document.body);
const resultHandle = await page.evaluateHandle(body => body.innerHTML, aHandle);
console.log(await resultHandle.jsonValue());
await resultHandle.dispose();
```
Most of the time this function returns a [JSHandle](./puppeteer.jshandle.md)<!-- -->, but if `pageFunction` returns a reference to an element, you instead get an [ElementHandle](./puppeteer.elementhandle.md) back:
## Example 3
```
const button = await page.evaluateHandle(() => document.querySelector('button'));
// can call `click` because `button` is an `ElementHandle`
await button.click();
```
The TypeScript definitions assume that `evaluateHandle` returns a `JSHandle`<!-- -->, but if you know it's going to return an `ElementHandle`<!-- -->, pass it as the generic argument:
```
const button = await page.evaluateHandle<ElementHandle>(...);
```

View File

@ -12,7 +12,7 @@ waitFor(selectorOrFunctionOrTimeout: string | number | Function, options?: {
hidden?: boolean;
timeout?: number;
polling?: string | number;
}, ...args: unknown[]): Promise<JSHandle>;
}, ...args: SerializableOrJSHandle[]): Promise<JSHandle>;
```
## Parameters
@ -21,7 +21,7 @@ waitFor(selectorOrFunctionOrTimeout: string | number | Function, options?: {
| --- | --- | --- |
| selectorOrFunctionOrTimeout | string \| number \| Function | |
| options | { visible?: boolean; hidden?: boolean; timeout?: number; polling?: string \| number; } | |
| args | unknown\[\] | |
| args | [SerializableOrJSHandle](./puppeteer.serializableorjshandle.md)<!-- -->\[\] | |
<b>Returns:</b>

View File

@ -10,7 +10,7 @@
waitForFunction(pageFunction: Function | string, options?: {
timeout?: number;
polling?: string | number;
}, ...args: unknown[]): Promise<JSHandle>;
}, ...args: SerializableOrJSHandle[]): Promise<JSHandle>;
```
## Parameters
@ -19,7 +19,7 @@ waitForFunction(pageFunction: Function | string, options?: {
| --- | --- | --- |
| pageFunction | Function \| string | |
| options | { timeout?: number; polling?: string \| number; } | |
| args | unknown\[\] | |
| args | [SerializableOrJSHandle](./puppeteer.serializableorjshandle.md)<!-- -->\[\] | |
<b>Returns:</b>

View File

@ -9,15 +9,15 @@ The only difference between `worker.evaluate` and `worker.evaluateHandle` is tha
<b>Signature:</b>
```typescript
evaluateHandle(pageFunction: Function | string, ...args: any[]): Promise<JSHandle>;
evaluateHandle<HandlerType extends JSHandle = JSHandle>(pageFunction: EvaluateHandleFn, ...args: SerializableOrJSHandle[]): Promise<JSHandle>;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| pageFunction | Function \| string | Function to be evaluated in the page context. |
| args | any\[\] | Arguments to pass to <code>pageFunction</code>. |
| pageFunction | [EvaluateHandleFn](./puppeteer.evaluatehandlefn.md) | Function to be evaluated in the page context. |
| args | [SerializableOrJSHandle](./puppeteer.serializableorjshandle.md)<!-- -->\[\] | Arguments to pass to <code>pageFunction</code>. |
<b>Returns:</b>

View File

@ -24,7 +24,11 @@ import { TimeoutSettings } from './TimeoutSettings';
import { MouseButtonInput } from './Input';
import { FrameManager, Frame } from './FrameManager';
import { getQueryHandlerAndSelector, QueryHandler } from './QueryHandler';
import { EvaluateFn, SerializableOrJSHandle } from './EvalTypes';
import {
EvaluateFn,
SerializableOrJSHandle,
EvaluateHandleFn,
} from './EvalTypes';
import { isNode } from '../environment';
// This predicateQueryHandler is declared here so that TypeScript knows about it
@ -103,15 +107,10 @@ export class DOMWorld {
return this._contextPromise;
}
/**
* @param {Function|string} pageFunction
* @param {!Array<*>} args
* @returns {!Promise<!JSHandle>}
*/
async evaluateHandle(
pageFunction: Function | string,
...args: unknown[]
): Promise<JSHandle> {
async evaluateHandle<HandlerType extends JSHandle = JSHandle>(
pageFunction: EvaluateHandleFn,
...args: SerializableOrJSHandle[]
): Promise<HandlerType> {
const context = await this.executionContext();
return context.evaluateHandle(pageFunction, ...args);
}
@ -470,7 +469,7 @@ export class DOMWorld {
waitForFunction(
pageFunction: Function | string,
options: { polling?: string | number; timeout?: number } = {},
...args: unknown[]
...args: SerializableOrJSHandle[]
): Promise<JSHandle> {
const {
polling = 'raf',
@ -581,7 +580,7 @@ class WaitTask {
_polling: string | number;
_timeout: number;
_predicateBody: string;
_args: unknown[];
_args: SerializableOrJSHandle[];
_runCount = 0;
promise: Promise<JSHandle>;
_resolve: (x: JSHandle) => void;
@ -596,7 +595,7 @@ class WaitTask {
title: string,
polling: string | number,
timeout: number,
...args: unknown[]
...args: SerializableOrJSHandle[]
) {
if (helper.isString(polling))
assert(

View File

@ -29,6 +29,11 @@ export type EvaluateFnReturnType<T extends EvaluateFn> = T extends (
? R
: unknown;
/**
* @public
*/
export type EvaluateHandleFn = string | ((...args: unknown[]) => unknown);
/**
* @public
*/

View File

@ -21,6 +21,7 @@ import { CDPSession } from './Connection';
import { DOMWorld } from './DOMWorld';
import { Frame } from './FrameManager';
import Protocol from '../protocol';
import { EvaluateHandleFn, SerializableOrJSHandle } from './EvalTypes';
export const EVALUATION_SCRIPT_URL = '__puppeteer_evaluation_script__';
const SOURCE_URL_REGEX = /^[\040\t]*\/\/[@#] sourceURL=\s*(\S*?)\s*$/m;
@ -175,15 +176,15 @@ export class ExecutionContext {
* @returns A promise that resolves to the return value of the given function
* as an in-page object (a {@link JSHandle}).
*/
async evaluateHandle(
pageFunction: Function | string,
...args: unknown[]
): Promise<JSHandle> {
return this._evaluateInternal<JSHandle>(false, pageFunction, ...args);
async evaluateHandle<HandleType extends JSHandle | ElementHandle = JSHandle>(
pageFunction: EvaluateHandleFn,
...args: SerializableOrJSHandle[]
): Promise<HandleType> {
return this._evaluateInternal<HandleType>(false, pageFunction, ...args);
}
private async _evaluateInternal<ReturnType>(
returnByValue,
returnByValue: boolean,
pageFunction: Function | string,
...args: unknown[]
): Promise<ReturnType> {

View File

@ -29,7 +29,11 @@ import { MouseButtonInput } from './Input';
import { Page } from './Page';
import { HTTPResponse } from './HTTPResponse';
import Protocol from '../protocol';
import { EvaluateFn, SerializableOrJSHandle } from './EvalTypes';
import {
EvaluateFn,
SerializableOrJSHandle,
EvaluateHandleFn,
} from './EvalTypes';
const UTILITY_WORLD_NAME = '__puppeteer_utility_world__';
@ -431,11 +435,11 @@ export class Frame {
return this._mainWorld.executionContext();
}
async evaluateHandle(
pageFunction: Function | string,
...args: unknown[]
): Promise<JSHandle> {
return this._mainWorld.evaluateHandle(pageFunction, ...args);
async evaluateHandle<HandlerType extends JSHandle = JSHandle>(
pageFunction: EvaluateHandleFn,
...args: SerializableOrJSHandle[]
): Promise<HandlerType> {
return this._mainWorld.evaluateHandle<HandlerType>(pageFunction, ...args);
}
async evaluate<ReturnType extends any>(
@ -562,7 +566,7 @@ export class Frame {
waitFor(
selectorOrFunctionOrTimeout: string | number | Function,
options: {} = {},
...args: unknown[]
...args: SerializableOrJSHandle[]
): Promise<JSHandle | null> {
const xPathPattern = '//';
@ -619,7 +623,7 @@ export class Frame {
waitForFunction(
pageFunction: Function | string,
options: { polling?: string | number; timeout?: number } = {},
...args: unknown[]
...args: SerializableOrJSHandle[]
): Promise<JSHandle> {
return this._mainWorld.waitForFunction(pageFunction, options, ...args);
}

View File

@ -27,6 +27,7 @@ import {
EvaluateFn,
SerializableOrJSHandle,
EvaluateFnReturnType,
EvaluateHandleFn,
} from './EvalTypes';
export interface BoxModel {
@ -174,10 +175,10 @@ export class JSHandle {
*
* See {@link Page.evaluateHandle} for more details.
*/
async evaluateHandle(
pageFunction: Function | string,
...args: unknown[]
): Promise<JSHandle> {
async evaluateHandle<HandleType extends JSHandle | ElementHandle = JSHandle>(
pageFunction: EvaluateHandleFn,
...args: SerializableOrJSHandle[]
): Promise<HandleType> {
return await this.executionContext().evaluateHandle(
pageFunction,
this,
@ -891,19 +892,22 @@ export class ElementHandle extends JSHandle {
* @param expression - Expression to {@link https://developer.mozilla.org/en-US/docs/Web/API/Document/evaluate | evaluate}
*/
async $x(expression: string): Promise<ElementHandle[]> {
const arrayHandle = await this.evaluateHandle((element, expression) => {
const document = element.ownerDocument || element;
const iterator = document.evaluate(
expression,
element,
null,
XPathResult.ORDERED_NODE_ITERATOR_TYPE
);
const array = [];
let item;
while ((item = iterator.iterateNext())) array.push(item);
return array;
}, expression);
const arrayHandle = await this.evaluateHandle(
(element: Document, expression: string) => {
const document = element.ownerDocument || element;
const iterator = document.evaluate(
expression,
element,
null,
XPathResult.ORDERED_NODE_ITERATOR_TYPE
);
const array = [];
let item;
while ((item = iterator.iterateNext())) array.push(item);
return array;
},
expression
);
const properties = await arrayHandle.getProperties();
await arrayHandle.dispose();
const result = [];

View File

@ -42,7 +42,11 @@ import { FileChooser } from './FileChooser';
import { ConsoleMessage, ConsoleMessageType } from './ConsoleMessage';
import { PuppeteerLifeCycleEvent } from './LifecycleWatcher';
import Protocol from '../protocol';
import { EvaluateFn, SerializableOrJSHandle } from './EvalTypes';
import {
EvaluateFn,
SerializableOrJSHandle,
EvaluateHandleFn,
} from './EvalTypes';
const writeFileAsync = promisify(fs.writeFile);
@ -639,12 +643,61 @@ export class Page extends EventEmitter {
return this.mainFrame().$(selector);
}
async evaluateHandle(
pageFunction: Function | string,
...args: unknown[]
): Promise<JSHandle> {
/**
* @remarks
*
* The only difference between {@link Page.evaluate | page.evaluate} and
* `page.evaluateHandle` is that `evaluateHandle` will return the value
* wrapped in an in-page object.
*
* If the function passed to `page.evaluteHandle` returns a Promise, the
* function will wait for the promise to resolve and return its value.
*
* You can pass a string instead of a function (although functions are
* recommended as they are easier to debug and use with TypeScript):
*
* @example
* ```
* const aHandle = await page.evaluateHandle('document')
* ```
*
* @example
* {@link JSHandle} instances can be passed as arguments to the `pageFunction`:
* ```
* const aHandle = await page.evaluateHandle(() => document.body);
* const resultHandle = await page.evaluateHandle(body => body.innerHTML, aHandle);
* console.log(await resultHandle.jsonValue());
* await resultHandle.dispose();
* ```
*
* Most of the time this function returns a {@link JSHandle},
* but if `pageFunction` returns a reference to an element,
* you instead get an {@link ElementHandle} back:
*
* @example
* ```
* const button = await page.evaluateHandle(() => document.querySelector('button'));
* // can call `click` because `button` is an `ElementHandle`
* await button.click();
* ```
*
* The TypeScript definitions assume that `evaluateHandle` returns
* a `JSHandle`, but if you know it's going to return an
* `ElementHandle`, pass it as the generic argument:
*
* ```
* const button = await page.evaluateHandle<ElementHandle>(...);
* ```
*
* @param pageFunction - a function that is run within the page
* @param args - arguments to be passed to the pageFunction
*/
async evaluateHandle<HandlerType extends JSHandle = JSHandle>(
pageFunction: EvaluateHandleFn,
...args: SerializableOrJSHandle[]
): Promise<HandlerType> {
const context = await this.mainFrame().executionContext();
return context.evaluateHandle(pageFunction, ...args);
return context.evaluateHandle<HandlerType>(pageFunction, ...args);
}
async queryObjects(prototypeHandle: JSHandle): Promise<JSHandle> {
@ -1471,7 +1524,7 @@ export class Page extends EventEmitter {
timeout?: number;
polling?: string | number;
} = {},
...args: unknown[]
...args: SerializableOrJSHandle[]
): Promise<JSHandle> {
return this.mainFrame().waitFor(
selectorOrFunctionOrTimeout,
@ -1508,7 +1561,7 @@ export class Page extends EventEmitter {
timeout?: number;
polling?: string | number;
} = {},
...args: unknown[]
...args: SerializableOrJSHandle[]
): Promise<JSHandle> {
return this.mainFrame().waitForFunction(pageFunction, options, ...args);
}

View File

@ -19,6 +19,7 @@ import { ExecutionContext } from './ExecutionContext';
import { JSHandle } from './JSHandle';
import { CDPSession } from './Connection';
import Protocol from '../protocol';
import { EvaluateHandleFn, SerializableOrJSHandle } from './EvalTypes';
type ConsoleAPICalledCallback = (
eventType: string,
@ -152,11 +153,11 @@ export class WebWorker extends EventEmitter {
* @param args - Arguments to pass to `pageFunction`.
* @returns Promise which resolves to the return value of `pageFunction`.
*/
async evaluateHandle(
pageFunction: Function | string,
...args: any[]
async evaluateHandle<HandlerType extends JSHandle = JSHandle>(
pageFunction: EvaluateHandleFn,
...args: SerializableOrJSHandle[]
): Promise<JSHandle> {
return (await this._executionContextPromise).evaluateHandle(
return (await this._executionContextPromise).evaluateHandle<HandlerType>(
pageFunction,
...args
);

View File

@ -182,17 +182,11 @@ describe('ElementHandle specs', function () {
const { page, server } = getTestState();
await page.goto(server.PREFIX + '/shadow.html');
const buttonHandle = await page.evaluateHandle(
const buttonHandle = await page.evaluateHandle<ElementHandle>(
// @ts-expect-error button is expected to be in the page's scope.
() => button
);
// TODO (@jackfranklin): TS types are off here. evaluateHandle returns a
// JSHandle but that doesn't have a click() method. In this case it seems
// to return an ElementHandle. I'm not sure if the tests are wrong here
// and should use evaluate<ElementHandle> or if the type of evaluateHandle
// should change to enable the user to tell us they are expecting an
// ElementHandle rather than the default JSHandle.
await (buttonHandle as ElementHandle).click();
await buttonHandle.click();
expect(
await page.evaluate(
// @ts-expect-error clicked is expected to be in the page's scope.

View File

@ -53,6 +53,7 @@ describe('JSHandle', function () {
const aHandle = await page.evaluateHandle(() => document.body);
let error = null;
await page
// @ts-expect-error we are deliberately passing a bad type here (nested object)
.evaluateHandle((opts) => opts.elem.querySelector('p'), {
elem: aHandle,
})