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:
Jack Franklin 2020-07-02 10:09:34 +01:00 committed by GitHub
parent 8370ec88ae
commit 6474edb9ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 282 additions and 72 deletions

View File

@ -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\[\]) =&gt; ReturnType \| Promise&lt;ReturnType&gt; | |
| args | [SerializableOrJSHandle](./puppeteer.serializableorjshandle.md)<!-- -->\[\] | | | args | [SerializableOrJSHandle](./puppeteer.serializableorjshandle.md)<!-- -->\[\] | |
<b>Returns:</b> <b>Returns:</b>
Promise&lt;ReturnType&gt; Promise&lt;[WrapElementHandle](./puppeteer.wrapelementhandle.md)<!-- -->&lt;ReturnType&gt;&gt;
## Example ## Example

View File

@ -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)<!-- -->&lt;ElementType&gt; \| null

View File

@ -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

View File

@ -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\[\]) =&gt; ReturnType \| Promise&lt;ReturnType&gt; | |
| args | [SerializableOrJSHandle](./puppeteer.serializableorjshandle.md)<!-- -->\[\] | | | args | [SerializableOrJSHandle](./puppeteer.serializableorjshandle.md)<!-- -->\[\] | |
<b>Returns:</b> <b>Returns:</b>
Promise&lt;ReturnType&gt; Promise&lt;[WrapElementHandle](./puppeteer.wrapelementhandle.md)<!-- -->&lt;ReturnType&gt;&gt;

View File

@ -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

View File

@ -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 |

View File

@ -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\[\]) =&gt; ReturnType \| Promise&lt;ReturnType&gt; | 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&lt;ReturnType&gt; Promise&lt;[WrapElementHandle](./puppeteer.wrapelementhandle.md)<!-- -->&lt;ReturnType&gt;&gt;
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
);
```

View File

@ -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) | | |

View File

@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [puppeteer](./puppeteer.md) &gt; [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;
```

View File

@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [puppeteer](./puppeteer.md) &gt; [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;
```

View File

@ -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);
} }

View File

@ -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;

View File

@ -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);
} }

View File

@ -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>;
} }
/** /**

View File

@ -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);
} }

View File

@ -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');

View File

@ -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;

View File

@ -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();

View File

@ -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
) )

View File

@ -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"`

View File

@ -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.

View File

@ -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',
]); ]);
/** /**