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>
|
||||
|
||||
```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
|
||||
@ -19,12 +19,12 @@ $eval<ReturnType extends any>(selector: string, pageFunction: EvaluateFn | strin
|
||||
| Parameter | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| selector | string | |
|
||||
| pageFunction | [EvaluateFn](./puppeteer.evaluatefn.md) \| string | |
|
||||
| pageFunction | (element: Element, ...args: unknown\[\]) => ReturnType \| Promise<ReturnType> | |
|
||||
| args | [SerializableOrJSHandle](./puppeteer.serializableorjshandle.md)<!-- -->\[\] | |
|
||||
|
||||
<b>Returns:</b>
|
||||
|
||||
Promise<ReturnType>
|
||||
Promise<[WrapElementHandle](./puppeteer.wrapelementhandle.md)<!-- --><ReturnType>>
|
||||
|
||||
## Example
|
||||
|
||||
|
@ -7,9 +7,9 @@
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
asElement(): ElementHandle | null;
|
||||
asElement(): ElementHandle<ElementType> | null;
|
||||
```
|
||||
<b>Returns:</b>
|
||||
|
||||
[ElementHandle](./puppeteer.elementhandle.md) \| null
|
||||
[ElementHandle](./puppeteer.elementhandle.md)<!-- --><ElementType> \| null
|
||||
|
||||
|
@ -9,7 +9,7 @@ ElementHandle represents an in-page DOM element.
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export declare class ElementHandle extends JSHandle
|
||||
export declare class ElementHandle<ElementType extends Element = Element> extends JSHandle
|
||||
```
|
||||
<b>Extends:</b> [JSHandle](./puppeteer.jshandle.md)
|
||||
|
||||
@ -34,6 +34,8 @@ ElementHandle prevents the DOM element from being garbage-collected unless the h
|
||||
|
||||
ElementHandle instances can be used as arguments in [Page.$eval()](./puppeteer.page._eval.md) and [Page.evaluate()](./puppeteer.page.evaluate.md) methods.
|
||||
|
||||
If you're using TypeScript, ElementHandle takes a generic argument that denotes the type of element the handle is holding within. For example, if you have a handle to a `<select>` element, you can type it as `ElementHandle<HTMLSelectElement>` and you get some nicer type checks.
|
||||
|
||||
The constructor for this class is marked as internal. Third-party code should not call the constructor directly or create subclasses that extend the `ElementHandle` class.
|
||||
|
||||
## Methods
|
||||
|
@ -7,7 +7,7 @@
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
$eval<ReturnType extends any>(selector: string, pageFunction: EvaluateFn | string, ...args: SerializableOrJSHandle[]): Promise<ReturnType>;
|
||||
$eval<ReturnType>(selector: string, pageFunction: (element: Element, ...args: unknown[]) => ReturnType | Promise<ReturnType>, ...args: SerializableOrJSHandle[]): Promise<WrapElementHandle<ReturnType>>;
|
||||
```
|
||||
|
||||
## Parameters
|
||||
@ -15,10 +15,10 @@ $eval<ReturnType extends any>(selector: string, pageFunction: EvaluateFn | strin
|
||||
| Parameter | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| selector | string | |
|
||||
| pageFunction | [EvaluateFn](./puppeteer.evaluatefn.md) \| string | |
|
||||
| pageFunction | (element: Element, ...args: unknown\[\]) => ReturnType \| Promise<ReturnType> | |
|
||||
| args | [SerializableOrJSHandle](./puppeteer.serializableorjshandle.md)<!-- -->\[\] | |
|
||||
|
||||
<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>
|
||||
|
||||
```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
|
||||
|
@ -89,4 +89,6 @@
|
||||
| [PuppeteerErrors](./puppeteer.puppeteererrors.md) | |
|
||||
| [Serializable](./puppeteer.serializable.md) | |
|
||||
| [SerializableOrJSHandle](./puppeteer.serializableorjshandle.md) | |
|
||||
| [UnwrapElementHandle](./puppeteer.unwrapelementhandle.md) | Unwraps a DOM element out of an ElementHandle instance |
|
||||
| [WrapElementHandle](./puppeteer.wrapelementhandle.md) | Wraps a DOM element into an ElementHandle instance |
|
||||
|
||||
|
@ -4,21 +4,65 @@
|
||||
|
||||
## Page.$eval() method
|
||||
|
||||
This method runs `document.querySelector` within the page and passes the result as the first argument to the `pageFunction`<!-- -->.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
$eval<ReturnType extends any>(selector: string, pageFunction: EvaluateFn | string, ...args: SerializableOrJSHandle[]): Promise<ReturnType>;
|
||||
$eval<ReturnType>(selector: string, pageFunction: (element: Element, ...args: unknown[]) => ReturnType | Promise<ReturnType>, ...args: SerializableOrJSHandle[]): Promise<WrapElementHandle<ReturnType>>;
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| selector | string | |
|
||||
| pageFunction | [EvaluateFn](./puppeteer.evaluatefn.md) \| string | |
|
||||
| args | [SerializableOrJSHandle](./puppeteer.serializableorjshandle.md)<!-- -->\[\] | |
|
||||
| selector | string | the [selector](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors) to query for |
|
||||
| pageFunction | (element: Element, ...args: unknown\[\]) => 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)<!-- -->\[\] | any additional arguments to pass through to <code>pageFunction</code>. |
|
||||
|
||||
<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) | | |
|
||||
| [$$eval(selector, pageFunction, args)](./puppeteer.page.__eval.md) | | |
|
||||
| [$eval(selector, pageFunction, args)](./puppeteer.page._eval.md) | | |
|
||||
| [$eval(selector, pageFunction, args)](./puppeteer.page._eval.md) | | This method runs <code>document.querySelector</code> within the page and passes the result as the first argument to the <code>pageFunction</code>. |
|
||||
| [$x(expression)](./puppeteer.page._x.md) | | |
|
||||
| [addScriptTag(options)](./puppeteer.page.addscripttag.md) | | |
|
||||
| [addStyleTag(options)](./puppeteer.page.addstyletag.md) | | |
|
||||
|
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,
|
||||
SerializableOrJSHandle,
|
||||
EvaluateHandleFn,
|
||||
WrapElementHandle,
|
||||
} from './EvalTypes';
|
||||
import { isNode } from '../environment';
|
||||
|
||||
@ -153,11 +154,14 @@ export class DOMWorld {
|
||||
return value;
|
||||
}
|
||||
|
||||
async $eval<ReturnType extends any>(
|
||||
async $eval<ReturnType>(
|
||||
selector: string,
|
||||
pageFunction: EvaluateFn | string,
|
||||
pageFunction: (
|
||||
element: Element,
|
||||
...args: unknown[]
|
||||
) => ReturnType | Promise<ReturnType>,
|
||||
...args: SerializableOrJSHandle[]
|
||||
): Promise<ReturnType> {
|
||||
): Promise<WrapElementHandle<ReturnType>> {
|
||||
const document = await this._document();
|
||||
return document.$eval<ReturnType>(selector, pageFunction, ...args);
|
||||
}
|
||||
|
@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { JSHandle } from './JSHandle';
|
||||
import { JSHandle, ElementHandle } from './JSHandle';
|
||||
|
||||
/**
|
||||
* @public
|
||||
@ -61,3 +61,15 @@ export interface JSONObject {
|
||||
* @public
|
||||
*/
|
||||
export type SerializableOrJSHandle = Serializable | JSHandle;
|
||||
|
||||
/**
|
||||
* Wraps a DOM element into an ElementHandle instance
|
||||
* @public
|
||||
**/
|
||||
export type WrapElementHandle<X> = X extends Element ? ElementHandle<X> : X;
|
||||
|
||||
/**
|
||||
* Unwraps a DOM element out of an ElementHandle instance
|
||||
* @public
|
||||
**/
|
||||
export type UnwrapElementHandle<X> = X extends ElementHandle<infer E> ? E : X;
|
||||
|
@ -33,6 +33,7 @@ import {
|
||||
EvaluateFn,
|
||||
SerializableOrJSHandle,
|
||||
EvaluateHandleFn,
|
||||
WrapElementHandle,
|
||||
} from './EvalTypes';
|
||||
|
||||
const UTILITY_WORLD_NAME = '__puppeteer_utility_world__';
|
||||
@ -457,11 +458,14 @@ export class Frame {
|
||||
return this._mainWorld.$x(expression);
|
||||
}
|
||||
|
||||
async $eval<ReturnType extends any>(
|
||||
async $eval<ReturnType>(
|
||||
selector: string,
|
||||
pageFunction: EvaluateFn | string,
|
||||
pageFunction: (
|
||||
element: Element,
|
||||
...args: unknown[]
|
||||
) => ReturnType | Promise<ReturnType>,
|
||||
...args: SerializableOrJSHandle[]
|
||||
): Promise<ReturnType> {
|
||||
): Promise<WrapElementHandle<ReturnType>> {
|
||||
return this._mainWorld.$eval<ReturnType>(selector, pageFunction, ...args);
|
||||
}
|
||||
|
||||
|
@ -28,6 +28,7 @@ import {
|
||||
SerializableOrJSHandle,
|
||||
EvaluateFnReturnType,
|
||||
EvaluateHandleFn,
|
||||
WrapElementHandle,
|
||||
} from './EvalTypes';
|
||||
|
||||
export interface BoxModel {
|
||||
@ -175,7 +176,7 @@ export class JSHandle {
|
||||
*
|
||||
* See {@link Page.evaluateHandle} for more details.
|
||||
*/
|
||||
async evaluateHandle<HandleType extends JSHandle | ElementHandle = JSHandle>(
|
||||
async evaluateHandle<HandleType extends JSHandle = JSHandle>(
|
||||
pageFunction: EvaluateHandleFn,
|
||||
...args: SerializableOrJSHandle[]
|
||||
): Promise<HandleType> {
|
||||
@ -316,9 +317,16 @@ export class JSHandle {
|
||||
* ElementHandle instances can be used as arguments in {@link Page.$eval} and
|
||||
* {@link Page.evaluate} methods.
|
||||
*
|
||||
* If you're using TypeScript, ElementHandle takes a generic argument that
|
||||
* denotes the type of element the handle is holding within. For example, if you
|
||||
* have a handle to a `<select>` element, you can type it as
|
||||
* `ElementHandle<HTMLSelectElement>` and you get some nicer type checks.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export class ElementHandle extends JSHandle {
|
||||
export class ElementHandle<
|
||||
ElementType extends Element = Element
|
||||
> extends JSHandle {
|
||||
private _page: Page;
|
||||
private _frameManager: FrameManager;
|
||||
|
||||
@ -339,7 +347,7 @@ export class ElementHandle extends JSHandle {
|
||||
this._frameManager = frameManager;
|
||||
}
|
||||
|
||||
asElement(): ElementHandle | null {
|
||||
asElement(): ElementHandle<ElementType> | null {
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -358,7 +366,7 @@ export class ElementHandle extends JSHandle {
|
||||
private async _scrollIntoViewIfNeeded(): Promise<void> {
|
||||
const error = await this.evaluate<
|
||||
(
|
||||
element: HTMLElement,
|
||||
element: Element,
|
||||
pageJavascriptEnabled: boolean
|
||||
) => Promise<string | false>
|
||||
>(async (element, pageJavascriptEnabled) => {
|
||||
@ -512,11 +520,9 @@ export class ElementHandle extends JSHandle {
|
||||
'"'
|
||||
);
|
||||
|
||||
/* TODO(jacktfranklin@): once ExecutionContext is TypeScript, and
|
||||
* its evaluate function is properly typed with generics we can
|
||||
* return here and remove the typecasting
|
||||
*/
|
||||
return this.evaluate((element: HTMLSelectElement, values: string[]) => {
|
||||
return this.evaluate<
|
||||
(element: HTMLSelectElement, values: string[]) => string[]
|
||||
>((element, values) => {
|
||||
if (element.nodeName.toLowerCase() !== 'select')
|
||||
throw new Error('Element is not a <select> element.');
|
||||
|
||||
@ -582,7 +588,7 @@ export class ElementHandle extends JSHandle {
|
||||
// not actually update the files in that case, so the solution is to eval the element
|
||||
// value to a new FileList directly.
|
||||
if (files.length === 0) {
|
||||
await this.evaluate((element: HTMLInputElement) => {
|
||||
await this.evaluate<(element: HTMLInputElement) => void>((element) => {
|
||||
element.files = new DataTransfer().files;
|
||||
|
||||
// Dispatch events for this case because it should behave akin to a user action.
|
||||
@ -821,22 +827,36 @@ export class ElementHandle extends JSHandle {
|
||||
* expect(await tweetHandle.$eval('.retweets', node => node.innerText)).toBe('10');
|
||||
* ```
|
||||
*/
|
||||
async $eval<ReturnType extends any>(
|
||||
async $eval<ReturnType>(
|
||||
selector: string,
|
||||
pageFunction: EvaluateFn | string,
|
||||
pageFunction: (
|
||||
element: Element,
|
||||
...args: unknown[]
|
||||
) => ReturnType | Promise<ReturnType>,
|
||||
...args: SerializableOrJSHandle[]
|
||||
): Promise<ReturnType> {
|
||||
): Promise<WrapElementHandle<ReturnType>> {
|
||||
const elementHandle = await this.$(selector);
|
||||
if (!elementHandle)
|
||||
throw new Error(
|
||||
`Error: failed to find element matching selector "${selector}"`
|
||||
);
|
||||
const result = await elementHandle.evaluate<(...args: any[]) => ReturnType>(
|
||||
pageFunction,
|
||||
...args
|
||||
);
|
||||
const result = await elementHandle.evaluate<
|
||||
(
|
||||
element: Element,
|
||||
...args: SerializableOrJSHandle[]
|
||||
) => ReturnType | Promise<ReturnType>
|
||||
>(pageFunction, ...args);
|
||||
await elementHandle.dispose();
|
||||
return result;
|
||||
|
||||
/**
|
||||
* This as is a little unfortunate but helps TS understand the behavour of
|
||||
* `elementHandle.evaluate`. If evalute returns an element it will return an
|
||||
* ElementHandle instance, rather than the plain object. All the
|
||||
* WrapElementHandle type does is wrap ReturnType into
|
||||
* ElementHandle<ReturnType> if it is an ElementHandle, or leave it alone as
|
||||
* ReturnType if it isn't.
|
||||
*/
|
||||
return result as WrapElementHandle<ReturnType>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -46,6 +46,7 @@ import {
|
||||
EvaluateFn,
|
||||
SerializableOrJSHandle,
|
||||
EvaluateHandleFn,
|
||||
WrapElementHandle,
|
||||
} from './EvalTypes';
|
||||
|
||||
const writeFileAsync = promisify(fs.writeFile);
|
||||
@ -705,11 +706,82 @@ export class Page extends EventEmitter {
|
||||
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,
|
||||
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[]
|
||||
): Promise<ReturnType> {
|
||||
): Promise<WrapElementHandle<ReturnType>> {
|
||||
return this.mainFrame().$eval<ReturnType>(selector, pageFunction, ...args);
|
||||
}
|
||||
|
||||
|
@ -322,7 +322,7 @@ describe('Page.click', function () {
|
||||
server.CROSS_PROCESS_PREFIX + '/input/button.html'
|
||||
);
|
||||
const frame = page.frames()[1];
|
||||
await frame.$eval('button', (button) =>
|
||||
await frame.$eval('button', (button: HTMLElement) =>
|
||||
button.style.setProperty('position', 'fixed')
|
||||
);
|
||||
await frame.click('button');
|
||||
|
@ -140,7 +140,7 @@ describe('input tests', function () {
|
||||
const [fileChooser1, fileChooser2] = await Promise.all([
|
||||
page.waitForFileChooser(),
|
||||
page.waitForFileChooser(),
|
||||
page.$eval('input', (input) => input.click()),
|
||||
page.$eval('input', (input: HTMLInputElement) => input.click()),
|
||||
]);
|
||||
expect(fileChooser1 === fileChooser2).toBe(true);
|
||||
});
|
||||
@ -161,10 +161,18 @@ describe('input tests', function () {
|
||||
chooser.accept([FILE_TO_UPLOAD]),
|
||||
new Promise((x) => page.once('metrics', x)),
|
||||
]);
|
||||
expect(await page.$eval('input', (input) => input.files.length)).toBe(1);
|
||||
expect(await page.$eval('input', (input) => input.files[0].name)).toBe(
|
||||
'file-to-upload.txt'
|
||||
);
|
||||
expect(
|
||||
await page.$eval(
|
||||
'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 () => {
|
||||
const { page } = getTestState();
|
||||
@ -174,7 +182,7 @@ describe('input tests', function () {
|
||||
.waitForFileChooser()
|
||||
.then((chooser) => chooser.accept([FILE_TO_UPLOAD]));
|
||||
expect(
|
||||
await page.$eval('input', async (picker) => {
|
||||
await page.$eval('input', async (picker: HTMLInputElement) => {
|
||||
picker.click();
|
||||
await new Promise((x) => (picker.oninput = x));
|
||||
const reader = new FileReader();
|
||||
@ -192,7 +200,7 @@ describe('input tests', function () {
|
||||
.waitForFileChooser()
|
||||
.then((chooser) => chooser.accept([FILE_TO_UPLOAD]));
|
||||
expect(
|
||||
await page.$eval('input', async (picker) => {
|
||||
await page.$eval('input', async (picker: HTMLInputElement) => {
|
||||
picker.click();
|
||||
await new Promise((x) => (picker.oninput = x));
|
||||
return picker.files.length;
|
||||
@ -200,7 +208,7 @@ describe('input tests', function () {
|
||||
).toBe(1);
|
||||
page.waitForFileChooser().then((chooser) => chooser.accept([]));
|
||||
expect(
|
||||
await page.$eval('input', async (picker) => {
|
||||
await page.$eval('input', async (picker: HTMLInputElement) => {
|
||||
picker.click();
|
||||
await new Promise((x) => (picker.oninput = x));
|
||||
return picker.files.length;
|
||||
@ -247,7 +255,7 @@ describe('input tests', function () {
|
||||
await page.setContent(`<input type=file>`);
|
||||
const [fileChooser] = await Promise.all([
|
||||
page.waitForFileChooser(),
|
||||
page.$eval('input', (input) => input.click()),
|
||||
page.$eval('input', (input: HTMLInputElement) => input.click()),
|
||||
]);
|
||||
await fileChooser.accept([]);
|
||||
let error = null;
|
||||
@ -268,13 +276,13 @@ describe('input tests', function () {
|
||||
await page.setContent(`<input type=file>`);
|
||||
const [fileChooser1] = await Promise.all([
|
||||
page.waitForFileChooser(),
|
||||
page.$eval('input', (input) => input.click()),
|
||||
page.$eval('input', (input: HTMLInputElement) => input.click()),
|
||||
]);
|
||||
await fileChooser1.cancel();
|
||||
// If this resolves, than we successfully canceled file chooser.
|
||||
await Promise.all([
|
||||
page.waitForFileChooser(),
|
||||
page.$eval('input', (input) => input.click()),
|
||||
page.$eval('input', (input: HTMLInputElement) => input.click()),
|
||||
]);
|
||||
});
|
||||
it('should fail when canceling file chooser twice', async () => {
|
||||
@ -283,7 +291,7 @@ describe('input tests', function () {
|
||||
await page.setContent(`<input type=file>`);
|
||||
const [fileChooser] = await Promise.all([
|
||||
page.waitForFileChooser(),
|
||||
page.$eval('input', (input) => input.click()),
|
||||
page.$eval('input', (input: HTMLInputElement) => input.click()),
|
||||
]);
|
||||
await fileChooser.cancel();
|
||||
let error = null;
|
||||
|
@ -351,9 +351,12 @@ describe('Keyboard', function () {
|
||||
|
||||
await page.goto(server.PREFIX + '/input/textarea.html');
|
||||
await page.type('textarea', '👹 Tokyo street Japan 🇯🇵');
|
||||
expect(await page.$eval('textarea', (textarea) => textarea.value)).toBe(
|
||||
'👹 Tokyo street Japan 🇯🇵'
|
||||
);
|
||||
expect(
|
||||
await page.$eval(
|
||||
'textarea',
|
||||
(textarea: HTMLInputElement) => textarea.value
|
||||
)
|
||||
).toBe('👹 Tokyo street Japan 🇯🇵');
|
||||
});
|
||||
itFailsFirefox('should type emoji into an iframe', async () => {
|
||||
const { page, server } = getTestState();
|
||||
@ -367,9 +370,12 @@ describe('Keyboard', function () {
|
||||
const frame = page.frames()[1];
|
||||
const textarea = await frame.$('textarea');
|
||||
await textarea.type('👹 Tokyo street Japan 🇯🇵');
|
||||
expect(await frame.$eval('textarea', (textarea) => textarea.value)).toBe(
|
||||
'👹 Tokyo street Japan 🇯🇵'
|
||||
);
|
||||
expect(
|
||||
await frame.$eval(
|
||||
'textarea',
|
||||
(textarea: HTMLInputElement) => textarea.value
|
||||
)
|
||||
).toBe('👹 Tokyo street Japan 🇯🇵');
|
||||
});
|
||||
itFailsFirefox('should press the meta key', async () => {
|
||||
const { page, isFirefox } = getTestState();
|
||||
|
@ -211,7 +211,7 @@ describe('Page', function () {
|
||||
);
|
||||
const [popup] = await Promise.all([
|
||||
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 popup.evaluate(() => !!window.opener)).toBe(false);
|
||||
@ -1616,7 +1616,7 @@ describe('Page', function () {
|
||||
await page.select('select', 'blue', 'black', 'magenta');
|
||||
await page.select('select');
|
||||
expect(
|
||||
await page.$eval('select', (select) =>
|
||||
await page.$eval('select', (select: HTMLSelectElement) =>
|
||||
Array.from(select.options).every(
|
||||
(option: HTMLOptionElement) => !option.selected
|
||||
)
|
||||
@ -1630,7 +1630,7 @@ describe('Page', function () {
|
||||
await page.select('select', 'blue', 'black', 'magenta');
|
||||
await page.select('select');
|
||||
expect(
|
||||
await page.$eval('select', (select) =>
|
||||
await page.$eval('select', (select: HTMLSelectElement) =>
|
||||
Array.from(select.options).every(
|
||||
(option: HTMLOptionElement) => !option.selected
|
||||
)
|
||||
|
@ -49,7 +49,7 @@ describe('querySelector', function () {
|
||||
const divHandle = await page.$('div');
|
||||
const text = await page.$eval(
|
||||
'section',
|
||||
(e, div) => e.textContent + div.textContent,
|
||||
(e, div: HTMLElement) => e.textContent + div.textContent,
|
||||
divHandle
|
||||
);
|
||||
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>'
|
||||
);
|
||||
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');
|
||||
});
|
||||
|
||||
@ -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>';
|
||||
await page.setContent(htmlContent);
|
||||
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');
|
||||
});
|
||||
|
||||
@ -197,7 +203,7 @@ describe('querySelector', function () {
|
||||
await page.setContent(htmlContent);
|
||||
const elementHandle = await page.$('#myId');
|
||||
const errorMessage = await elementHandle
|
||||
.$eval('.a', (node) => node.innerText)
|
||||
.$eval('.a', (node: HTMLElement) => node.innerText)
|
||||
.catch((error) => error.message);
|
||||
expect(errorMessage).toBe(
|
||||
`Error: failed to find element matching selector ".a"`
|
||||
|
@ -65,7 +65,7 @@ describe('request interception', function () {
|
||||
</form>
|
||||
`);
|
||||
await Promise.all([
|
||||
page.$eval('form', (form) => form.submit()),
|
||||
page.$eval('form', (form: HTMLFormElement) => form.submit()),
|
||||
page.waitForNavigation(),
|
||||
]);
|
||||
});
|
||||
@ -454,7 +454,7 @@ describe('request interception', function () {
|
||||
page.on('request', async (r) => (request = r));
|
||||
page.$eval(
|
||||
'iframe',
|
||||
(frame, url) => (frame.src = url),
|
||||
(frame: HTMLIFrameElement, url: string) => (frame.src = url),
|
||||
server.EMPTY_PAGE
|
||||
),
|
||||
// Wait for request interception.
|
||||
|
@ -25,6 +25,10 @@ const EXCLUDE_PROPERTIES = new Set([
|
||||
'Page.create',
|
||||
'JSHandle.toString',
|
||||
'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