diff --git a/new-docs/puppeteer.elementhandle.__eval.md b/new-docs/puppeteer.elementhandle.__eval.md
index f351b5ef..ccacd727 100644
--- a/new-docs/puppeteer.elementhandle.__eval.md
+++ b/new-docs/puppeteer.elementhandle.__eval.md
@@ -11,7 +11,7 @@ If `pageFunction` returns a Promise, then `frame.$$eval` would wait for the prom
Signature:
```typescript
-$$eval(selector: string, pageFunction: EvaluateFn | string, ...args: SerializableOrJSHandle[]): Promise;
+$$eval(selector: string, pageFunction: (elements: Element[], ...args: unknown[]) => ReturnType | Promise, ...args: SerializableOrJSHandle[]): Promise>;
```
## Parameters
@@ -19,12 +19,12 @@ $$eval(selector: string, pageFunction: EvaluateFn | stri
| Parameter | Type | Description |
| --- | --- | --- |
| selector | string | |
-| pageFunction | [EvaluateFn](./puppeteer.evaluatefn.md) \| string | |
+| pageFunction | (elements: Element\[\], ...args: unknown\[\]) => ReturnType \| Promise<ReturnType> | |
| args | [SerializableOrJSHandle](./puppeteer.serializableorjshandle.md)\[\] | |
Returns:
-Promise<ReturnType>
+Promise<[WrapElementHandle](./puppeteer.wrapelementhandle.md)<ReturnType>>
## Example 1
diff --git a/new-docs/puppeteer.frame.__eval.md b/new-docs/puppeteer.frame.__eval.md
index 889af679..f441635c 100644
--- a/new-docs/puppeteer.frame.__eval.md
+++ b/new-docs/puppeteer.frame.__eval.md
@@ -7,7 +7,7 @@
Signature:
```typescript
-$$eval(selector: string, pageFunction: EvaluateFn | string, ...args: SerializableOrJSHandle[]): Promise;
+$$eval(selector: string, pageFunction: (elements: Element[], ...args: unknown[]) => ReturnType | Promise, ...args: SerializableOrJSHandle[]): Promise>;
```
## Parameters
@@ -15,10 +15,10 @@ $$eval(selector: string, pageFunction: EvaluateFn | stri
| Parameter | Type | Description |
| --- | --- | --- |
| selector | string | |
-| pageFunction | [EvaluateFn](./puppeteer.evaluatefn.md) \| string | |
+| pageFunction | (elements: Element\[\], ...args: unknown\[\]) => ReturnType \| Promise<ReturnType> | |
| args | [SerializableOrJSHandle](./puppeteer.serializableorjshandle.md)\[\] | |
Returns:
-Promise<ReturnType>
+Promise<[WrapElementHandle](./puppeteer.wrapelementhandle.md)<ReturnType>>
diff --git a/new-docs/puppeteer.page.__eval.md b/new-docs/puppeteer.page.__eval.md
index 0d9fd00e..f191006a 100644
--- a/new-docs/puppeteer.page.__eval.md
+++ b/new-docs/puppeteer.page.__eval.md
@@ -9,7 +9,7 @@ This method runs `Array.from(document.querySelectorAll(selector))` within the pa
Signature:
```typescript
-$$eval(selector: string, pageFunction: EvaluateFn | string, ...args: SerializableOrJSHandle[]): Promise;
+$$eval(selector: string, pageFunction: (elements: Element[], ...args: unknown[]) => ReturnType | Promise, ...args: SerializableOrJSHandle[]): Promise>;
```
## Parameters
@@ -17,14 +17,14 @@ $$eval(selector: string, pageFunction: EvaluateFn | stri
| 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 Array.from(document.querySelectorAll(selector))
as its first argument. |
+| pageFunction | (elements: Element\[\], ...args: unknown\[\]) => ReturnType \| Promise<ReturnType> | the function to be evaluated in the page context. Will be passed the result of Array.from(document.querySelectorAll(selector))
as its first argument. |
| args | [SerializableOrJSHandle](./puppeteer.serializableorjshandle.md)\[\] | any additional arguments to pass through to pageFunction
. |
Returns:
-Promise<ReturnType>
+Promise<[WrapElementHandle](./puppeteer.wrapelementhandle.md)<ReturnType>>
-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
@@ -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(
+ 'input', (elements: HTMLInputElement[]) => elements.map(e => e.textContent)
+);
```
diff --git a/src/common/DOMWorld.ts b/src/common/DOMWorld.ts
index 1cf644f8..f5c075ec 100644
--- a/src/common/DOMWorld.ts
+++ b/src/common/DOMWorld.ts
@@ -25,7 +25,6 @@ import { MouseButton } from './Input';
import { FrameManager, Frame } from './FrameManager';
import { getQueryHandlerAndSelector, QueryHandler } from './QueryHandler';
import {
- EvaluateFn,
SerializableOrJSHandle,
EvaluateHandleFn,
WrapElementHandle,
@@ -172,11 +171,14 @@ export class DOMWorld {
return document.$eval(selector, pageFunction, ...args);
}
- async $$eval(
+ async $$eval(
selector: string,
- pageFunction: EvaluateFn | string,
+ pageFunction: (
+ elements: Element[],
+ ...args: unknown[]
+ ) => ReturnType | Promise,
...args: SerializableOrJSHandle[]
- ): Promise {
+ ): Promise> {
const document = await this._document();
const value = await document.$$eval(
selector,
diff --git a/src/common/FrameManager.ts b/src/common/FrameManager.ts
index f6c1aa9a..a50ac3b4 100644
--- a/src/common/FrameManager.ts
+++ b/src/common/FrameManager.ts
@@ -30,7 +30,6 @@ import { Page } from './Page';
import { HTTPResponse } from './HTTPResponse';
import Protocol from '../protocol';
import {
- EvaluateFn,
SerializableOrJSHandle,
EvaluateHandleFn,
WrapElementHandle,
@@ -496,11 +495,14 @@ export class Frame {
return this._mainWorld.$eval(selector, pageFunction, ...args);
}
- async $$eval(
+ async $$eval(
selector: string,
- pageFunction: EvaluateFn | string,
+ pageFunction: (
+ elements: Element[],
+ ...args: unknown[]
+ ) => ReturnType | Promise,
...args: SerializableOrJSHandle[]
- ): Promise {
+ ): Promise> {
return this._mainWorld.$$eval(selector, pageFunction, ...args);
}
diff --git a/src/common/JSHandle.ts b/src/common/JSHandle.ts
index d7b8f668..082d7d76 100644
--- a/src/common/JSHandle.ts
+++ b/src/common/JSHandle.ts
@@ -882,11 +882,14 @@ export class ElementHandle<
* .toEqual(['Hello!', 'Hi!']);
* ```
*/
- async $$eval(
+ async $$eval(
selector: string,
- pageFunction: EvaluateFn | string,
+ pageFunction: (
+ elements: Element[],
+ ...args: unknown[]
+ ) => ReturnType | Promise,
...args: SerializableOrJSHandle[]
- ): Promise {
+ ): Promise> {
const defaultHandler = (element: Element, selector: string) =>
Array.from(element.querySelectorAll(selector));
const { updatedSelector, queryHandler } = getQueryHandlerAndSelector(
@@ -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
+ >(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;
}
/**
diff --git a/src/common/Page.ts b/src/common/Page.ts
index 34c02d5b..6e3cc49b 100644
--- a/src/common/Page.ts
+++ b/src/common/Page.ts
@@ -43,7 +43,6 @@ import { ConsoleMessage, ConsoleMessageType } from './ConsoleMessage';
import { PuppeteerLifeCycleEvent } from './LifecycleWatcher';
import Protocol from '../protocol';
import {
- EvaluateFn,
SerializableOrJSHandle,
EvaluateHandleFn,
WrapElementHandle,
@@ -795,31 +794,70 @@ export class Page extends EventEmitter {
* resolve and then return its value.
*
* @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
- * ```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(
+ * '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.
- * 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(
+ async $$eval(
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,
...args: SerializableOrJSHandle[]
- ): Promise {
+ ): Promise> {
return this.mainFrame().$$eval(selector, pageFunction, ...args);
}
diff --git a/test/queryselector.spec.ts b/test/queryselector.spec.ts
index db2e36e8..df484e0b 100644
--- a/test/queryselector.spec.ts
+++ b/test/queryselector.spec.ts
@@ -218,7 +218,7 @@ describe('querySelector', function () {
''
);
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']);
@@ -231,7 +231,7 @@ describe('querySelector', function () {
'not-a-child-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']);