diff --git a/docs/api.md b/docs/api.md index 7d28d79089c85..17934bad43539 100644 --- a/docs/api.md +++ b/docs/api.md @@ -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. diff --git a/new-docs/puppeteer.evaluatehandlefn.md b/new-docs/puppeteer.evaluatehandlefn.md new file mode 100644 index 0000000000000..65596a1f6bcd4 --- /dev/null +++ b/new-docs/puppeteer.evaluatehandlefn.md @@ -0,0 +1,12 @@ + + +[Home](./index.md) > [puppeteer](./puppeteer.md) > [EvaluateHandleFn](./puppeteer.evaluatehandlefn.md) + +## EvaluateHandleFn type + + +Signature: + +```typescript +export declare type EvaluateHandleFn = string | ((...args: unknown[]) => unknown); +``` diff --git a/new-docs/puppeteer.executioncontext.evaluatehandle.md b/new-docs/puppeteer.executioncontext.evaluatehandle.md index 7730949164dc4..3f89892f0a9fa 100644 --- a/new-docs/puppeteer.executioncontext.evaluatehandle.md +++ b/new-docs/puppeteer.executioncontext.evaluatehandle.md @@ -7,19 +7,19 @@ Signature: ```typescript -evaluateHandle(pageFunction: Function | string, ...args: unknown[]): Promise; +evaluateHandle(pageFunction: EvaluateHandleFn, ...args: SerializableOrJSHandle[]): Promise; ``` ## Parameters | Parameter | Type | Description | | --- | --- | --- | -| pageFunction | Function \| string | a function to be evaluated in the executionContext | -| args | unknown\[\] | argument to pass to the page function | +| pageFunction | [EvaluateHandleFn](./puppeteer.evaluatehandlefn.md) | a function to be evaluated in the executionContext | +| args | [SerializableOrJSHandle](./puppeteer.serializableorjshandle.md)\[\] | argument to pass to the page function | Returns: -Promise<[JSHandle](./puppeteer.jshandle.md)> +Promise<HandleType> A promise that resolves to the return value of the given function as an in-page object (a [JSHandle](./puppeteer.jshandle.md)). diff --git a/new-docs/puppeteer.frame.evaluatehandle.md b/new-docs/puppeteer.frame.evaluatehandle.md index 57b20326df5ff..83e1ee4510842 100644 --- a/new-docs/puppeteer.frame.evaluatehandle.md +++ b/new-docs/puppeteer.frame.evaluatehandle.md @@ -7,17 +7,17 @@ Signature: ```typescript -evaluateHandle(pageFunction: Function | string, ...args: unknown[]): Promise; +evaluateHandle(pageFunction: EvaluateHandleFn, ...args: SerializableOrJSHandle[]): Promise; ``` ## Parameters | Parameter | Type | Description | | --- | --- | --- | -| pageFunction | Function \| string | | -| args | unknown\[\] | | +| pageFunction | [EvaluateHandleFn](./puppeteer.evaluatehandlefn.md) | | +| args | [SerializableOrJSHandle](./puppeteer.serializableorjshandle.md)\[\] | | Returns: -Promise<[JSHandle](./puppeteer.jshandle.md)> +Promise<HandlerType> diff --git a/new-docs/puppeteer.frame.waitfor.md b/new-docs/puppeteer.frame.waitfor.md index 52a4dc4710a76..9f540b13b12b4 100644 --- a/new-docs/puppeteer.frame.waitfor.md +++ b/new-docs/puppeteer.frame.waitfor.md @@ -7,7 +7,7 @@ Signature: ```typescript -waitFor(selectorOrFunctionOrTimeout: string | number | Function, options?: {}, ...args: unknown[]): Promise; +waitFor(selectorOrFunctionOrTimeout: string | number | Function, options?: {}, ...args: SerializableOrJSHandle[]): Promise; ``` ## Parameters @@ -16,7 +16,7 @@ waitFor(selectorOrFunctionOrTimeout: string | number | Function, options?: {}, . | --- | --- | --- | | selectorOrFunctionOrTimeout | string \| number \| Function | | | options | {} | | -| args | unknown\[\] | | +| args | [SerializableOrJSHandle](./puppeteer.serializableorjshandle.md)\[\] | | Returns: diff --git a/new-docs/puppeteer.frame.waitforfunction.md b/new-docs/puppeteer.frame.waitforfunction.md index 93692db5809f0..5c98f20ce8091 100644 --- a/new-docs/puppeteer.frame.waitforfunction.md +++ b/new-docs/puppeteer.frame.waitforfunction.md @@ -10,7 +10,7 @@ waitForFunction(pageFunction: Function | string, options?: { polling?: string | number; timeout?: number; - }, ...args: unknown[]): Promise; + }, ...args: SerializableOrJSHandle[]): Promise; ``` ## 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)\[\] | | Returns: diff --git a/new-docs/puppeteer.jshandle.evaluatehandle.md b/new-docs/puppeteer.jshandle.evaluatehandle.md index 460786707b2eb..a217d35938827 100644 --- a/new-docs/puppeteer.jshandle.evaluatehandle.md +++ b/new-docs/puppeteer.jshandle.evaluatehandle.md @@ -9,19 +9,19 @@ This method passes this handle as the first argument to `pageFunction`. Signature: ```typescript -evaluateHandle(pageFunction: Function | string, ...args: unknown[]): Promise; +evaluateHandle(pageFunction: EvaluateHandleFn, ...args: SerializableOrJSHandle[]): Promise; ``` ## Parameters | Parameter | Type | Description | | --- | --- | --- | -| pageFunction | Function \| string | | -| args | unknown\[\] | | +| pageFunction | [EvaluateHandleFn](./puppeteer.evaluatehandlefn.md) | | +| args | [SerializableOrJSHandle](./puppeteer.serializableorjshandle.md)\[\] | | Returns: -Promise<[JSHandle](./puppeteer.jshandle.md)> +Promise<HandleType> ## Remarks diff --git a/new-docs/puppeteer.md b/new-docs/puppeteer.md index 13db4adb2f1f2..9e0f572d7618a 100644 --- a/new-docs/puppeteer.md +++ b/new-docs/puppeteer.md @@ -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) | | diff --git a/new-docs/puppeteer.page.evaluatehandle.md b/new-docs/puppeteer.page.evaluatehandle.md index 6d1142de3366d..5c56ce2453573 100644 --- a/new-docs/puppeteer.page.evaluatehandle.md +++ b/new-docs/puppeteer.page.evaluatehandle.md @@ -7,17 +7,62 @@ Signature: ```typescript -evaluateHandle(pageFunction: Function | string, ...args: unknown[]): Promise; +evaluateHandle(pageFunction: EvaluateHandleFn, ...args: SerializableOrJSHandle[]): Promise; ``` ## 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 | Returns: -Promise<[JSHandle](./puppeteer.jshandle.md)> +Promise<HandlerType> + +## 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(...); + +``` diff --git a/new-docs/puppeteer.page.waitfor.md b/new-docs/puppeteer.page.waitfor.md index 8a2eb518ab408..e808df9af69a8 100644 --- a/new-docs/puppeteer.page.waitfor.md +++ b/new-docs/puppeteer.page.waitfor.md @@ -12,7 +12,7 @@ waitFor(selectorOrFunctionOrTimeout: string | number | Function, options?: { hidden?: boolean; timeout?: number; polling?: string | number; - }, ...args: unknown[]): Promise; + }, ...args: SerializableOrJSHandle[]): Promise; ``` ## 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)\[\] | | Returns: diff --git a/new-docs/puppeteer.page.waitforfunction.md b/new-docs/puppeteer.page.waitforfunction.md index 697ff57dfc71b..e18be0769aeaa 100644 --- a/new-docs/puppeteer.page.waitforfunction.md +++ b/new-docs/puppeteer.page.waitforfunction.md @@ -10,7 +10,7 @@ waitForFunction(pageFunction: Function | string, options?: { timeout?: number; polling?: string | number; - }, ...args: unknown[]): Promise; + }, ...args: SerializableOrJSHandle[]): Promise; ``` ## 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)\[\] | | Returns: diff --git a/new-docs/puppeteer.webworker.evaluatehandle.md b/new-docs/puppeteer.webworker.evaluatehandle.md index f187a930aa831..aeb5f78519232 100644 --- a/new-docs/puppeteer.webworker.evaluatehandle.md +++ b/new-docs/puppeteer.webworker.evaluatehandle.md @@ -9,15 +9,15 @@ The only difference between `worker.evaluate` and `worker.evaluateHandle` is tha Signature: ```typescript -evaluateHandle(pageFunction: Function | string, ...args: any[]): Promise; +evaluateHandle(pageFunction: EvaluateHandleFn, ...args: SerializableOrJSHandle[]): Promise; ``` ## Parameters | Parameter | Type | Description | | --- | --- | --- | -| pageFunction | Function \| string | Function to be evaluated in the page context. | -| args | any\[\] | Arguments to pass to pageFunction. | +| pageFunction | [EvaluateHandleFn](./puppeteer.evaluatehandlefn.md) | Function to be evaluated in the page context. | +| args | [SerializableOrJSHandle](./puppeteer.serializableorjshandle.md)\[\] | Arguments to pass to pageFunction. | Returns: diff --git a/src/common/DOMWorld.ts b/src/common/DOMWorld.ts index 3ea66d50ccd6f..ed7ee4b420f6a 100644 --- a/src/common/DOMWorld.ts +++ b/src/common/DOMWorld.ts @@ -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} - */ - async evaluateHandle( - pageFunction: Function | string, - ...args: unknown[] - ): Promise { + async evaluateHandle( + pageFunction: EvaluateHandleFn, + ...args: SerializableOrJSHandle[] + ): Promise { 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 { const { polling = 'raf', @@ -581,7 +580,7 @@ class WaitTask { _polling: string | number; _timeout: number; _predicateBody: string; - _args: unknown[]; + _args: SerializableOrJSHandle[]; _runCount = 0; promise: Promise; _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( diff --git a/src/common/EvalTypes.ts b/src/common/EvalTypes.ts index c898d22be478e..f8588245effab 100644 --- a/src/common/EvalTypes.ts +++ b/src/common/EvalTypes.ts @@ -29,6 +29,11 @@ export type EvaluateFnReturnType = T extends ( ? R : unknown; +/** + * @public + */ +export type EvaluateHandleFn = string | ((...args: unknown[]) => unknown); + /** * @public */ diff --git a/src/common/ExecutionContext.ts b/src/common/ExecutionContext.ts index 5681e9d0bc0af..df7d40246807d 100644 --- a/src/common/ExecutionContext.ts +++ b/src/common/ExecutionContext.ts @@ -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 { - return this._evaluateInternal(false, pageFunction, ...args); + async evaluateHandle( + pageFunction: EvaluateHandleFn, + ...args: SerializableOrJSHandle[] + ): Promise { + return this._evaluateInternal(false, pageFunction, ...args); } private async _evaluateInternal( - returnByValue, + returnByValue: boolean, pageFunction: Function | string, ...args: unknown[] ): Promise { diff --git a/src/common/FrameManager.ts b/src/common/FrameManager.ts index 859b649eaa92b..4ac5704449ead 100644 --- a/src/common/FrameManager.ts +++ b/src/common/FrameManager.ts @@ -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 { - return this._mainWorld.evaluateHandle(pageFunction, ...args); + async evaluateHandle( + pageFunction: EvaluateHandleFn, + ...args: SerializableOrJSHandle[] + ): Promise { + return this._mainWorld.evaluateHandle(pageFunction, ...args); } async evaluate( @@ -562,7 +566,7 @@ export class Frame { waitFor( selectorOrFunctionOrTimeout: string | number | Function, options: {} = {}, - ...args: unknown[] + ...args: SerializableOrJSHandle[] ): Promise { const xPathPattern = '//'; @@ -619,7 +623,7 @@ export class Frame { waitForFunction( pageFunction: Function | string, options: { polling?: string | number; timeout?: number } = {}, - ...args: unknown[] + ...args: SerializableOrJSHandle[] ): Promise { return this._mainWorld.waitForFunction(pageFunction, options, ...args); } diff --git a/src/common/JSHandle.ts b/src/common/JSHandle.ts index 2202d76ac0db9..51741ef49ed48 100644 --- a/src/common/JSHandle.ts +++ b/src/common/JSHandle.ts @@ -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 { + async evaluateHandle( + pageFunction: EvaluateHandleFn, + ...args: SerializableOrJSHandle[] + ): Promise { 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 { - 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 = []; diff --git a/src/common/Page.ts b/src/common/Page.ts index ad7a077dab82f..de9f90175cb43 100644 --- a/src/common/Page.ts +++ b/src/common/Page.ts @@ -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 { + /** + * @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(...); + * ``` + * + * @param pageFunction - a function that is run within the page + * @param args - arguments to be passed to the pageFunction + */ + async evaluateHandle( + pageFunction: EvaluateHandleFn, + ...args: SerializableOrJSHandle[] + ): Promise { const context = await this.mainFrame().executionContext(); - return context.evaluateHandle(pageFunction, ...args); + return context.evaluateHandle(pageFunction, ...args); } async queryObjects(prototypeHandle: JSHandle): Promise { @@ -1471,7 +1524,7 @@ export class Page extends EventEmitter { timeout?: number; polling?: string | number; } = {}, - ...args: unknown[] + ...args: SerializableOrJSHandle[] ): Promise { return this.mainFrame().waitFor( selectorOrFunctionOrTimeout, @@ -1508,7 +1561,7 @@ export class Page extends EventEmitter { timeout?: number; polling?: string | number; } = {}, - ...args: unknown[] + ...args: SerializableOrJSHandle[] ): Promise { return this.mainFrame().waitForFunction(pageFunction, options, ...args); } diff --git a/src/common/WebWorker.ts b/src/common/WebWorker.ts index 4b9e4ab3ff7cc..dacdd4ab159cd 100644 --- a/src/common/WebWorker.ts +++ b/src/common/WebWorker.ts @@ -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( + pageFunction: EvaluateHandleFn, + ...args: SerializableOrJSHandle[] ): Promise { - return (await this._executionContextPromise).evaluateHandle( + return (await this._executionContextPromise).evaluateHandle( pageFunction, ...args ); diff --git a/test/elementhandle.spec.ts b/test/elementhandle.spec.ts index 38af4b6648c87..fe6252b403902 100644 --- a/test/elementhandle.spec.ts +++ b/test/elementhandle.spec.ts @@ -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( // @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 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. diff --git a/test/jshandle.spec.ts b/test/jshandle.spec.ts index 0d66f3e4f128e..276d88b9f5830 100644 --- a/test/jshandle.spec.ts +++ b/test/jshandle.spec.ts @@ -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, })