mirror of
https://github.com/puppeteer/puppeteer
synced 2024-06-14 14:02:48 +00:00
feat(types): add types for $eval
(#6135)
This pulls in the types (based on the DefinitelyTyped repo) for `page.$eval` (and the `$eval` method on other classes). The `$eval` method is quite hard to type due to the way we wrap and unwrap ElementHandles that are passed to / returned from the `pageFunction` that users provide. Longer term we can improve the types by providing type overloads as DefinitelyTyped does but I've deferred that for now (see the `TODO` in the code for more details).
This commit is contained in:
parent
8370ec88ae
commit
6474edb9ba
@ -11,7 +11,7 @@ If `pageFunction` returns a Promise, then `frame.$eval` would wait for the promi
|
|||||||
<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: (element: 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 | strin
|
|||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| selector | string | |
|
| selector | string | |
|
||||||
| pageFunction | [EvaluateFn](./puppeteer.evaluatefn.md) \| string | |
|
| pageFunction | (element: Element, ...args: unknown\[\]) => ReturnType \| Promise<ReturnType> | |
|
||||||
| args | [SerializableOrJSHandle](./puppeteer.serializableorjshandle.md)<!-- -->\[\] | |
|
| args | [SerializableOrJSHandle](./puppeteer.serializableorjshandle.md)<!-- -->\[\] | |
|
||||||
|
|
||||||
<b>Returns:</b>
|
<b>Returns:</b>
|
||||||
|
|
||||||
Promise<ReturnType>
|
Promise<[WrapElementHandle](./puppeteer.wrapelementhandle.md)<!-- --><ReturnType>>
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
|
@ -7,9 +7,9 @@
|
|||||||
<b>Signature:</b>
|
<b>Signature:</b>
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
asElement(): ElementHandle | null;
|
asElement(): ElementHandle<ElementType> | null;
|
||||||
```
|
```
|
||||||
<b>Returns:</b>
|
<b>Returns:</b>
|
||||||
|
|
||||||
[ElementHandle](./puppeteer.elementhandle.md) \| null
|
[ElementHandle](./puppeteer.elementhandle.md)<!-- --><ElementType> \| null
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ ElementHandle represents an in-page DOM element.
|
|||||||
<b>Signature:</b>
|
<b>Signature:</b>
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
export declare class ElementHandle extends JSHandle
|
export declare class ElementHandle<ElementType extends Element = Element> extends JSHandle
|
||||||
```
|
```
|
||||||
<b>Extends:</b> [JSHandle](./puppeteer.jshandle.md)
|
<b>Extends:</b> [JSHandle](./puppeteer.jshandle.md)
|
||||||
|
|
||||||
@ -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.
|
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.
|
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
|
## Methods
|
||||||
|
@ -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: (element: 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 | strin
|
|||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| selector | string | |
|
| selector | string | |
|
||||||
| pageFunction | [EvaluateFn](./puppeteer.evaluatefn.md) \| string | |
|
| pageFunction | (element: Element, ...args: unknown\[\]) => ReturnType \| Promise<ReturnType> | |
|
||||||
| args | [SerializableOrJSHandle](./puppeteer.serializableorjshandle.md)<!-- -->\[\] | |
|
| args | [SerializableOrJSHandle](./puppeteer.serializableorjshandle.md)<!-- -->\[\] | |
|
||||||
|
|
||||||
<b>Returns:</b>
|
<b>Returns:</b>
|
||||||
|
|
||||||
Promise<ReturnType>
|
Promise<[WrapElementHandle](./puppeteer.wrapelementhandle.md)<!-- --><ReturnType>>
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ This method passes this handle as the first argument to `pageFunction`<!-- -->.
|
|||||||
<b>Signature:</b>
|
<b>Signature:</b>
|
||||||
|
|
||||||
```typescript
|
```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
|
## Parameters
|
||||||
|
@ -89,4 +89,6 @@
|
|||||||
| [PuppeteerErrors](./puppeteer.puppeteererrors.md) | |
|
| [PuppeteerErrors](./puppeteer.puppeteererrors.md) | |
|
||||||
| [Serializable](./puppeteer.serializable.md) | |
|
| [Serializable](./puppeteer.serializable.md) | |
|
||||||
| [SerializableOrJSHandle](./puppeteer.serializableorjshandle.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 |
|
||||||
|
|
||||||
|
@ -4,21 +4,65 @@
|
|||||||
|
|
||||||
## Page.$eval() method
|
## 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>
|
<b>Signature:</b>
|
||||||
|
|
||||||
```typescript
|
```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
|
## Parameters
|
||||||
|
|
||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| selector | string | |
|
| selector | string | the [selector](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors) to query for |
|
||||||
| pageFunction | [EvaluateFn](./puppeteer.evaluatefn.md) \| string | |
|
| pageFunction | (element: Element, ...args: unknown\[\]) => ReturnType \| Promise<ReturnType> | 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)<!-- -->\[\] | |
|
| args | [SerializableOrJSHandle](./puppeteer.serializableorjshandle.md)<!-- -->\[\] | any additional arguments to pass through to <code>pageFunction</code>. |
|
||||||
|
|
||||||
<b>Returns:</b>
|
<b>Returns:</b>
|
||||||
|
|
||||||
Promise<ReturnType>
|
Promise<[WrapElementHandle](./puppeteer.wrapelementhandle.md)<!-- --><ReturnType>>
|
||||||
|
|
||||||
|
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
|
||||||
|
);
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
@ -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) | | 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) | | |
|
| [$$(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) | | |
|
| [$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) | | |
|
| [$x(expression)](./puppeteer.page._x.md) | | |
|
||||||
| [addScriptTag(options)](./puppeteer.page.addscripttag.md) | | |
|
| [addScriptTag(options)](./puppeteer.page.addscripttag.md) | | |
|
||||||
| [addStyleTag(options)](./puppeteer.page.addstyletag.md) | | |
|
| [addStyleTag(options)](./puppeteer.page.addstyletag.md) | | |
|
||||||
|
13
new-docs/puppeteer.unwrapelementhandle.md
Normal file
13
new-docs/puppeteer.unwrapelementhandle.md
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||||
|
|
||||||
|
[Home](./index.md) > [puppeteer](./puppeteer.md) > [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
new-docs/puppeteer.wrapelementhandle.md
Normal file
13
new-docs/puppeteer.wrapelementhandle.md
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||||
|
|
||||||
|
[Home](./index.md) > [puppeteer](./puppeteer.md) > [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;
|
||||||
|
```
|
@ -28,6 +28,7 @@ import {
|
|||||||
EvaluateFn,
|
EvaluateFn,
|
||||||
SerializableOrJSHandle,
|
SerializableOrJSHandle,
|
||||||
EvaluateHandleFn,
|
EvaluateHandleFn,
|
||||||
|
WrapElementHandle,
|
||||||
} from './EvalTypes';
|
} from './EvalTypes';
|
||||||
import { isNode } from '../environment';
|
import { isNode } from '../environment';
|
||||||
|
|
||||||
@ -153,11 +154,14 @@ export class DOMWorld {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
async $eval<ReturnType extends any>(
|
async $eval<ReturnType>(
|
||||||
selector: string,
|
selector: string,
|
||||||
pageFunction: EvaluateFn | string,
|
pageFunction: (
|
||||||
|
element: 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();
|
||||||
return document.$eval<ReturnType>(selector, pageFunction, ...args);
|
return document.$eval<ReturnType>(selector, pageFunction, ...args);
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { JSHandle } from './JSHandle';
|
import { JSHandle, ElementHandle } from './JSHandle';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
@ -61,3 +61,15 @@ export interface JSONObject {
|
|||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export type SerializableOrJSHandle = Serializable | JSHandle;
|
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;
|
||||||
|
@ -33,6 +33,7 @@ import {
|
|||||||
EvaluateFn,
|
EvaluateFn,
|
||||||
SerializableOrJSHandle,
|
SerializableOrJSHandle,
|
||||||
EvaluateHandleFn,
|
EvaluateHandleFn,
|
||||||
|
WrapElementHandle,
|
||||||
} from './EvalTypes';
|
} from './EvalTypes';
|
||||||
|
|
||||||
const UTILITY_WORLD_NAME = '__puppeteer_utility_world__';
|
const UTILITY_WORLD_NAME = '__puppeteer_utility_world__';
|
||||||
@ -457,11 +458,14 @@ export class Frame {
|
|||||||
return this._mainWorld.$x(expression);
|
return this._mainWorld.$x(expression);
|
||||||
}
|
}
|
||||||
|
|
||||||
async $eval<ReturnType extends any>(
|
async $eval<ReturnType>(
|
||||||
selector: string,
|
selector: string,
|
||||||
pageFunction: EvaluateFn | string,
|
pageFunction: (
|
||||||
|
element: 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,6 +28,7 @@ import {
|
|||||||
SerializableOrJSHandle,
|
SerializableOrJSHandle,
|
||||||
EvaluateFnReturnType,
|
EvaluateFnReturnType,
|
||||||
EvaluateHandleFn,
|
EvaluateHandleFn,
|
||||||
|
WrapElementHandle,
|
||||||
} from './EvalTypes';
|
} from './EvalTypes';
|
||||||
|
|
||||||
export interface BoxModel {
|
export interface BoxModel {
|
||||||
@ -175,7 +176,7 @@ export class JSHandle {
|
|||||||
*
|
*
|
||||||
* See {@link Page.evaluateHandle} for more details.
|
* See {@link Page.evaluateHandle} for more details.
|
||||||
*/
|
*/
|
||||||
async evaluateHandle<HandleType extends JSHandle | ElementHandle = JSHandle>(
|
async evaluateHandle<HandleType extends JSHandle = JSHandle>(
|
||||||
pageFunction: EvaluateHandleFn,
|
pageFunction: EvaluateHandleFn,
|
||||||
...args: SerializableOrJSHandle[]
|
...args: SerializableOrJSHandle[]
|
||||||
): Promise<HandleType> {
|
): Promise<HandleType> {
|
||||||
@ -316,9 +317,16 @@ export class JSHandle {
|
|||||||
* ElementHandle instances can be used as arguments in {@link Page.$eval} and
|
* ElementHandle instances can be used as arguments in {@link Page.$eval} and
|
||||||
* {@link Page.evaluate} methods.
|
* {@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
|
* @public
|
||||||
*/
|
*/
|
||||||
export class ElementHandle extends JSHandle {
|
export class ElementHandle<
|
||||||
|
ElementType extends Element = Element
|
||||||
|
> extends JSHandle {
|
||||||
private _page: Page;
|
private _page: Page;
|
||||||
private _frameManager: FrameManager;
|
private _frameManager: FrameManager;
|
||||||
|
|
||||||
@ -339,7 +347,7 @@ export class ElementHandle extends JSHandle {
|
|||||||
this._frameManager = frameManager;
|
this._frameManager = frameManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
asElement(): ElementHandle | null {
|
asElement(): ElementHandle<ElementType> | null {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -358,7 +366,7 @@ export class ElementHandle extends JSHandle {
|
|||||||
private async _scrollIntoViewIfNeeded(): Promise<void> {
|
private async _scrollIntoViewIfNeeded(): Promise<void> {
|
||||||
const error = await this.evaluate<
|
const error = await this.evaluate<
|
||||||
(
|
(
|
||||||
element: HTMLElement,
|
element: Element,
|
||||||
pageJavascriptEnabled: boolean
|
pageJavascriptEnabled: boolean
|
||||||
) => Promise<string | false>
|
) => Promise<string | false>
|
||||||
>(async (element, pageJavascriptEnabled) => {
|
>(async (element, pageJavascriptEnabled) => {
|
||||||
@ -512,11 +520,9 @@ export class ElementHandle extends JSHandle {
|
|||||||
'"'
|
'"'
|
||||||
);
|
);
|
||||||
|
|
||||||
/* TODO(jacktfranklin@): once ExecutionContext is TypeScript, and
|
return this.evaluate<
|
||||||
* its evaluate function is properly typed with generics we can
|
(element: HTMLSelectElement, values: string[]) => string[]
|
||||||
* return here and remove the typecasting
|
>((element, values) => {
|
||||||
*/
|
|
||||||
return this.evaluate((element: HTMLSelectElement, values: string[]) => {
|
|
||||||
if (element.nodeName.toLowerCase() !== 'select')
|
if (element.nodeName.toLowerCase() !== 'select')
|
||||||
throw new Error('Element is not a <select> element.');
|
throw new Error('Element is not a <select> element.');
|
||||||
|
|
||||||
@ -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
|
// not actually update the files in that case, so the solution is to eval the element
|
||||||
// value to a new FileList directly.
|
// value to a new FileList directly.
|
||||||
if (files.length === 0) {
|
if (files.length === 0) {
|
||||||
await this.evaluate((element: HTMLInputElement) => {
|
await this.evaluate<(element: HTMLInputElement) => void>((element) => {
|
||||||
element.files = new DataTransfer().files;
|
element.files = new DataTransfer().files;
|
||||||
|
|
||||||
// Dispatch events for this case because it should behave akin to a user action.
|
// Dispatch events for this case because it should behave akin to a user action.
|
||||||
@ -821,22 +827,36 @@ export class ElementHandle extends JSHandle {
|
|||||||
* expect(await tweetHandle.$eval('.retweets', node => node.innerText)).toBe('10');
|
* expect(await tweetHandle.$eval('.retweets', node => node.innerText)).toBe('10');
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
async $eval<ReturnType extends any>(
|
async $eval<ReturnType>(
|
||||||
selector: string,
|
selector: string,
|
||||||
pageFunction: EvaluateFn | string,
|
pageFunction: (
|
||||||
|
element: Element,
|
||||||
|
...args: unknown[]
|
||||||
|
) => ReturnType | Promise<ReturnType>,
|
||||||
...args: SerializableOrJSHandle[]
|
...args: SerializableOrJSHandle[]
|
||||||
): Promise<ReturnType> {
|
): Promise<WrapElementHandle<ReturnType>> {
|
||||||
const elementHandle = await this.$(selector);
|
const elementHandle = await this.$(selector);
|
||||||
if (!elementHandle)
|
if (!elementHandle)
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Error: failed to find element matching selector "${selector}"`
|
`Error: failed to find element matching selector "${selector}"`
|
||||||
);
|
);
|
||||||
const result = await elementHandle.evaluate<(...args: any[]) => ReturnType>(
|
const result = await elementHandle.evaluate<
|
||||||
pageFunction,
|
(
|
||||||
...args
|
element: Element,
|
||||||
);
|
...args: SerializableOrJSHandle[]
|
||||||
|
) => ReturnType | Promise<ReturnType>
|
||||||
|
>(pageFunction, ...args);
|
||||||
await elementHandle.dispose();
|
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>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -46,6 +46,7 @@ import {
|
|||||||
EvaluateFn,
|
EvaluateFn,
|
||||||
SerializableOrJSHandle,
|
SerializableOrJSHandle,
|
||||||
EvaluateHandleFn,
|
EvaluateHandleFn,
|
||||||
|
WrapElementHandle,
|
||||||
} from './EvalTypes';
|
} from './EvalTypes';
|
||||||
|
|
||||||
const writeFileAsync = promisify(fs.writeFile);
|
const writeFileAsync = promisify(fs.writeFile);
|
||||||
@ -705,11 +706,82 @@ export class Page extends EventEmitter {
|
|||||||
return context.queryObjects(prototypeHandle);
|
return context.queryObjects(prototypeHandle);
|
||||||
}
|
}
|
||||||
|
|
||||||
async $eval<ReturnType extends any>(
|
/**
|
||||||
|
* This method runs `document.querySelector` within the page and passes the
|
||||||
|
* result as the first argument to the `pageFunction`.
|
||||||
|
*
|
||||||
|
* @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
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* // 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
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* // 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
|
||||||
|
* );
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @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 `document.querySelector(selector)` as its first
|
||||||
|
* argument.
|
||||||
|
* @param args any additional arguments to pass through to `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>(
|
||||||
selector: string,
|
selector: string,
|
||||||
pageFunction: EvaluateFn | string,
|
pageFunction: (
|
||||||
|
element: Element,
|
||||||
|
/* Unfortunately this has to be unknown[] because it's hard to get
|
||||||
|
* TypeScript to understand that the arguments will be left alone unless
|
||||||
|
* they are an ElementHandle, in which case they will be unwrapped.
|
||||||
|
* The nice thing about unknown vs any is that unknown will force the user
|
||||||
|
* to type the item before using it to avoid errors.
|
||||||
|
*
|
||||||
|
* TODO(@jackfranklin): We could fix this by using overloads like
|
||||||
|
* DefinitelyTyped does:
|
||||||
|
* https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/puppeteer/index.d.ts#L114
|
||||||
|
*/
|
||||||
|
...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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -322,7 +322,7 @@ describe('Page.click', function () {
|
|||||||
server.CROSS_PROCESS_PREFIX + '/input/button.html'
|
server.CROSS_PROCESS_PREFIX + '/input/button.html'
|
||||||
);
|
);
|
||||||
const frame = page.frames()[1];
|
const frame = page.frames()[1];
|
||||||
await frame.$eval('button', (button) =>
|
await frame.$eval('button', (button: HTMLElement) =>
|
||||||
button.style.setProperty('position', 'fixed')
|
button.style.setProperty('position', 'fixed')
|
||||||
);
|
);
|
||||||
await frame.click('button');
|
await frame.click('button');
|
||||||
|
@ -140,7 +140,7 @@ describe('input tests', function () {
|
|||||||
const [fileChooser1, fileChooser2] = await Promise.all([
|
const [fileChooser1, fileChooser2] = await Promise.all([
|
||||||
page.waitForFileChooser(),
|
page.waitForFileChooser(),
|
||||||
page.waitForFileChooser(),
|
page.waitForFileChooser(),
|
||||||
page.$eval('input', (input) => input.click()),
|
page.$eval('input', (input: HTMLInputElement) => input.click()),
|
||||||
]);
|
]);
|
||||||
expect(fileChooser1 === fileChooser2).toBe(true);
|
expect(fileChooser1 === fileChooser2).toBe(true);
|
||||||
});
|
});
|
||||||
@ -161,10 +161,18 @@ describe('input tests', function () {
|
|||||||
chooser.accept([FILE_TO_UPLOAD]),
|
chooser.accept([FILE_TO_UPLOAD]),
|
||||||
new Promise((x) => page.once('metrics', x)),
|
new Promise((x) => page.once('metrics', x)),
|
||||||
]);
|
]);
|
||||||
expect(await page.$eval('input', (input) => input.files.length)).toBe(1);
|
expect(
|
||||||
expect(await page.$eval('input', (input) => input.files[0].name)).toBe(
|
await page.$eval(
|
||||||
'file-to-upload.txt'
|
'input',
|
||||||
);
|
(input: HTMLInputElement) => input.files.length
|
||||||
|
)
|
||||||
|
).toBe(1);
|
||||||
|
expect(
|
||||||
|
await page.$eval(
|
||||||
|
'input',
|
||||||
|
(input: HTMLInputElement) => input.files[0].name
|
||||||
|
)
|
||||||
|
).toBe('file-to-upload.txt');
|
||||||
});
|
});
|
||||||
it('should be able to read selected file', async () => {
|
it('should be able to read selected file', async () => {
|
||||||
const { page } = getTestState();
|
const { page } = getTestState();
|
||||||
@ -174,7 +182,7 @@ describe('input tests', function () {
|
|||||||
.waitForFileChooser()
|
.waitForFileChooser()
|
||||||
.then((chooser) => chooser.accept([FILE_TO_UPLOAD]));
|
.then((chooser) => chooser.accept([FILE_TO_UPLOAD]));
|
||||||
expect(
|
expect(
|
||||||
await page.$eval('input', async (picker) => {
|
await page.$eval('input', async (picker: HTMLInputElement) => {
|
||||||
picker.click();
|
picker.click();
|
||||||
await new Promise((x) => (picker.oninput = x));
|
await new Promise((x) => (picker.oninput = x));
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
@ -192,7 +200,7 @@ describe('input tests', function () {
|
|||||||
.waitForFileChooser()
|
.waitForFileChooser()
|
||||||
.then((chooser) => chooser.accept([FILE_TO_UPLOAD]));
|
.then((chooser) => chooser.accept([FILE_TO_UPLOAD]));
|
||||||
expect(
|
expect(
|
||||||
await page.$eval('input', async (picker) => {
|
await page.$eval('input', async (picker: HTMLInputElement) => {
|
||||||
picker.click();
|
picker.click();
|
||||||
await new Promise((x) => (picker.oninput = x));
|
await new Promise((x) => (picker.oninput = x));
|
||||||
return picker.files.length;
|
return picker.files.length;
|
||||||
@ -200,7 +208,7 @@ describe('input tests', function () {
|
|||||||
).toBe(1);
|
).toBe(1);
|
||||||
page.waitForFileChooser().then((chooser) => chooser.accept([]));
|
page.waitForFileChooser().then((chooser) => chooser.accept([]));
|
||||||
expect(
|
expect(
|
||||||
await page.$eval('input', async (picker) => {
|
await page.$eval('input', async (picker: HTMLInputElement) => {
|
||||||
picker.click();
|
picker.click();
|
||||||
await new Promise((x) => (picker.oninput = x));
|
await new Promise((x) => (picker.oninput = x));
|
||||||
return picker.files.length;
|
return picker.files.length;
|
||||||
@ -247,7 +255,7 @@ describe('input tests', function () {
|
|||||||
await page.setContent(`<input type=file>`);
|
await page.setContent(`<input type=file>`);
|
||||||
const [fileChooser] = await Promise.all([
|
const [fileChooser] = await Promise.all([
|
||||||
page.waitForFileChooser(),
|
page.waitForFileChooser(),
|
||||||
page.$eval('input', (input) => input.click()),
|
page.$eval('input', (input: HTMLInputElement) => input.click()),
|
||||||
]);
|
]);
|
||||||
await fileChooser.accept([]);
|
await fileChooser.accept([]);
|
||||||
let error = null;
|
let error = null;
|
||||||
@ -268,13 +276,13 @@ describe('input tests', function () {
|
|||||||
await page.setContent(`<input type=file>`);
|
await page.setContent(`<input type=file>`);
|
||||||
const [fileChooser1] = await Promise.all([
|
const [fileChooser1] = await Promise.all([
|
||||||
page.waitForFileChooser(),
|
page.waitForFileChooser(),
|
||||||
page.$eval('input', (input) => input.click()),
|
page.$eval('input', (input: HTMLInputElement) => input.click()),
|
||||||
]);
|
]);
|
||||||
await fileChooser1.cancel();
|
await fileChooser1.cancel();
|
||||||
// If this resolves, than we successfully canceled file chooser.
|
// If this resolves, than we successfully canceled file chooser.
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
page.waitForFileChooser(),
|
page.waitForFileChooser(),
|
||||||
page.$eval('input', (input) => input.click()),
|
page.$eval('input', (input: HTMLInputElement) => input.click()),
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
it('should fail when canceling file chooser twice', async () => {
|
it('should fail when canceling file chooser twice', async () => {
|
||||||
@ -283,7 +291,7 @@ describe('input tests', function () {
|
|||||||
await page.setContent(`<input type=file>`);
|
await page.setContent(`<input type=file>`);
|
||||||
const [fileChooser] = await Promise.all([
|
const [fileChooser] = await Promise.all([
|
||||||
page.waitForFileChooser(),
|
page.waitForFileChooser(),
|
||||||
page.$eval('input', (input) => input.click()),
|
page.$eval('input', (input: HTMLInputElement) => input.click()),
|
||||||
]);
|
]);
|
||||||
await fileChooser.cancel();
|
await fileChooser.cancel();
|
||||||
let error = null;
|
let error = null;
|
||||||
|
@ -351,9 +351,12 @@ describe('Keyboard', function () {
|
|||||||
|
|
||||||
await page.goto(server.PREFIX + '/input/textarea.html');
|
await page.goto(server.PREFIX + '/input/textarea.html');
|
||||||
await page.type('textarea', '👹 Tokyo street Japan 🇯🇵');
|
await page.type('textarea', '👹 Tokyo street Japan 🇯🇵');
|
||||||
expect(await page.$eval('textarea', (textarea) => textarea.value)).toBe(
|
expect(
|
||||||
'👹 Tokyo street Japan 🇯🇵'
|
await page.$eval(
|
||||||
);
|
'textarea',
|
||||||
|
(textarea: HTMLInputElement) => textarea.value
|
||||||
|
)
|
||||||
|
).toBe('👹 Tokyo street Japan 🇯🇵');
|
||||||
});
|
});
|
||||||
itFailsFirefox('should type emoji into an iframe', async () => {
|
itFailsFirefox('should type emoji into an iframe', async () => {
|
||||||
const { page, server } = getTestState();
|
const { page, server } = getTestState();
|
||||||
@ -367,9 +370,12 @@ describe('Keyboard', function () {
|
|||||||
const frame = page.frames()[1];
|
const frame = page.frames()[1];
|
||||||
const textarea = await frame.$('textarea');
|
const textarea = await frame.$('textarea');
|
||||||
await textarea.type('👹 Tokyo street Japan 🇯🇵');
|
await textarea.type('👹 Tokyo street Japan 🇯🇵');
|
||||||
expect(await frame.$eval('textarea', (textarea) => textarea.value)).toBe(
|
expect(
|
||||||
'👹 Tokyo street Japan 🇯🇵'
|
await frame.$eval(
|
||||||
);
|
'textarea',
|
||||||
|
(textarea: HTMLInputElement) => textarea.value
|
||||||
|
)
|
||||||
|
).toBe('👹 Tokyo street Japan 🇯🇵');
|
||||||
});
|
});
|
||||||
itFailsFirefox('should press the meta key', async () => {
|
itFailsFirefox('should press the meta key', async () => {
|
||||||
const { page, isFirefox } = getTestState();
|
const { page, isFirefox } = getTestState();
|
||||||
|
@ -211,7 +211,7 @@ describe('Page', function () {
|
|||||||
);
|
);
|
||||||
const [popup] = await Promise.all([
|
const [popup] = await Promise.all([
|
||||||
new Promise<Page>((x) => page.once('popup', x)),
|
new Promise<Page>((x) => page.once('popup', x)),
|
||||||
page.$eval('a', (a) => a.click()),
|
page.$eval('a', (a: HTMLAnchorElement) => a.click()),
|
||||||
]);
|
]);
|
||||||
expect(await page.evaluate(() => !!window.opener)).toBe(false);
|
expect(await page.evaluate(() => !!window.opener)).toBe(false);
|
||||||
expect(await popup.evaluate(() => !!window.opener)).toBe(false);
|
expect(await popup.evaluate(() => !!window.opener)).toBe(false);
|
||||||
@ -1616,7 +1616,7 @@ describe('Page', function () {
|
|||||||
await page.select('select', 'blue', 'black', 'magenta');
|
await page.select('select', 'blue', 'black', 'magenta');
|
||||||
await page.select('select');
|
await page.select('select');
|
||||||
expect(
|
expect(
|
||||||
await page.$eval('select', (select) =>
|
await page.$eval('select', (select: HTMLSelectElement) =>
|
||||||
Array.from(select.options).every(
|
Array.from(select.options).every(
|
||||||
(option: HTMLOptionElement) => !option.selected
|
(option: HTMLOptionElement) => !option.selected
|
||||||
)
|
)
|
||||||
@ -1630,7 +1630,7 @@ describe('Page', function () {
|
|||||||
await page.select('select', 'blue', 'black', 'magenta');
|
await page.select('select', 'blue', 'black', 'magenta');
|
||||||
await page.select('select');
|
await page.select('select');
|
||||||
expect(
|
expect(
|
||||||
await page.$eval('select', (select) =>
|
await page.$eval('select', (select: HTMLSelectElement) =>
|
||||||
Array.from(select.options).every(
|
Array.from(select.options).every(
|
||||||
(option: HTMLOptionElement) => !option.selected
|
(option: HTMLOptionElement) => !option.selected
|
||||||
)
|
)
|
||||||
|
@ -49,7 +49,7 @@ describe('querySelector', function () {
|
|||||||
const divHandle = await page.$('div');
|
const divHandle = await page.$('div');
|
||||||
const text = await page.$eval(
|
const text = await page.$eval(
|
||||||
'section',
|
'section',
|
||||||
(e, div) => e.textContent + div.textContent,
|
(e, div: HTMLElement) => e.textContent + div.textContent,
|
||||||
divHandle
|
divHandle
|
||||||
);
|
);
|
||||||
expect(text).toBe('hello world');
|
expect(text).toBe('hello world');
|
||||||
@ -174,7 +174,10 @@ describe('querySelector', function () {
|
|||||||
'<html><body><div class="tweet"><div class="like">100</div><div class="retweets">10</div></div></body></html>'
|
'<html><body><div class="tweet"><div class="like">100</div><div class="retweets">10</div></div></body></html>'
|
||||||
);
|
);
|
||||||
const tweet = await page.$('.tweet');
|
const tweet = await page.$('.tweet');
|
||||||
const content = await tweet.$eval('.like', (node) => node.innerText);
|
const content = await tweet.$eval(
|
||||||
|
'.like',
|
||||||
|
(node: HTMLElement) => node.innerText
|
||||||
|
);
|
||||||
expect(content).toBe('100');
|
expect(content).toBe('100');
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -185,7 +188,10 @@ describe('querySelector', function () {
|
|||||||
'<div class="a">not-a-child-div</div><div id="myId"><div class="a">a-child-div</div></div>';
|
'<div class="a">not-a-child-div</div><div id="myId"><div class="a">a-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', (node) => node.innerText);
|
const content = await elementHandle.$eval(
|
||||||
|
'.a',
|
||||||
|
(node: HTMLElement) => node.innerText
|
||||||
|
);
|
||||||
expect(content).toBe('a-child-div');
|
expect(content).toBe('a-child-div');
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -197,7 +203,7 @@ describe('querySelector', function () {
|
|||||||
await page.setContent(htmlContent);
|
await page.setContent(htmlContent);
|
||||||
const elementHandle = await page.$('#myId');
|
const elementHandle = await page.$('#myId');
|
||||||
const errorMessage = await elementHandle
|
const errorMessage = await elementHandle
|
||||||
.$eval('.a', (node) => node.innerText)
|
.$eval('.a', (node: HTMLElement) => node.innerText)
|
||||||
.catch((error) => error.message);
|
.catch((error) => error.message);
|
||||||
expect(errorMessage).toBe(
|
expect(errorMessage).toBe(
|
||||||
`Error: failed to find element matching selector ".a"`
|
`Error: failed to find element matching selector ".a"`
|
||||||
|
@ -65,7 +65,7 @@ describe('request interception', function () {
|
|||||||
</form>
|
</form>
|
||||||
`);
|
`);
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
page.$eval('form', (form) => form.submit()),
|
page.$eval('form', (form: HTMLFormElement) => form.submit()),
|
||||||
page.waitForNavigation(),
|
page.waitForNavigation(),
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
@ -454,7 +454,7 @@ describe('request interception', function () {
|
|||||||
page.on('request', async (r) => (request = r));
|
page.on('request', async (r) => (request = r));
|
||||||
page.$eval(
|
page.$eval(
|
||||||
'iframe',
|
'iframe',
|
||||||
(frame, url) => (frame.src = url),
|
(frame: HTMLIFrameElement, url: string) => (frame.src = url),
|
||||||
server.EMPTY_PAGE
|
server.EMPTY_PAGE
|
||||||
),
|
),
|
||||||
// Wait for request interception.
|
// Wait for request interception.
|
||||||
|
@ -25,6 +25,10 @@ const EXCLUDE_PROPERTIES = new Set([
|
|||||||
'Page.create',
|
'Page.create',
|
||||||
'JSHandle.toString',
|
'JSHandle.toString',
|
||||||
'TimeoutError.name',
|
'TimeoutError.name',
|
||||||
|
/* This isn't an actual property, but a TypeScript generic.
|
||||||
|
* DocLint incorrectly parses it as a property.
|
||||||
|
*/
|
||||||
|
'ElementHandle.ElementType',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
Reference in New Issue
Block a user