mirror of
https://github.com/puppeteer/puppeteer
synced 2024-06-14 14:02:48 +00:00
feat(types): improve typing of .evaluate() (#6096)
* feat(types): improve typing of `.evaluate()` This is the start of the work to take the types from the `@types/puppeteer` repository and port them into our repo so we can ship our built-in types out the box. This change types the `evaluate` function properly. It takes a generic type which is the type of the function you're passing, and the arguments and the return that you get back from the `evaluate` call are typed correctly.
This commit is contained in:
@@ -48,3 +48,4 @@ export * from './common/Errors';
|
||||
export * from './common/Tracing';
|
||||
export * from './common/WebWorker';
|
||||
export * from './common/USKeyboardLayout';
|
||||
export * from './common/EvalTypes';
|
||||
|
||||
@@ -24,6 +24,7 @@ import { TimeoutSettings } from './TimeoutSettings';
|
||||
import { MouseButtonInput } from './Input';
|
||||
import { FrameManager, Frame } from './FrameManager';
|
||||
import { getQueryHandlerAndSelector, QueryHandler } from './QueryHandler';
|
||||
import { EvaluateFn, SerializableOrJSHandle } from './EvalTypes';
|
||||
import { isNode } from '../environment';
|
||||
|
||||
// This predicateQueryHandler is declared here so that TypeScript knows about it
|
||||
@@ -155,8 +156,8 @@ export class DOMWorld {
|
||||
|
||||
async $eval<ReturnType extends any>(
|
||||
selector: string,
|
||||
pageFunction: Function | string,
|
||||
...args: unknown[]
|
||||
pageFunction: EvaluateFn | string,
|
||||
...args: SerializableOrJSHandle[]
|
||||
): Promise<ReturnType> {
|
||||
const document = await this._document();
|
||||
return document.$eval<ReturnType>(selector, pageFunction, ...args);
|
||||
@@ -164,8 +165,8 @@ export class DOMWorld {
|
||||
|
||||
async $$eval<ReturnType extends any>(
|
||||
selector: string,
|
||||
pageFunction: Function | string,
|
||||
...args: unknown[]
|
||||
pageFunction: EvaluateFn | string,
|
||||
...args: SerializableOrJSHandle[]
|
||||
): Promise<ReturnType> {
|
||||
const document = await this._document();
|
||||
const value = await document.$$eval<ReturnType>(
|
||||
|
||||
58
src/common/EvalTypes.ts
Normal file
58
src/common/EvalTypes.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Copyright 2020 Google Inc. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { JSHandle } from './JSHandle';
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type EvaluateFn<T = any> = string | ((arg1: T, ...args: any[]) => any);
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type EvaluateFnReturnType<T extends EvaluateFn> = T extends (
|
||||
...args: any[]
|
||||
) => infer R
|
||||
? R
|
||||
: unknown;
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type Serializable =
|
||||
| number
|
||||
| string
|
||||
| boolean
|
||||
| null
|
||||
| JSONArray
|
||||
| JSONObject;
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type JSONArray = Serializable[];
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface JSONObject {
|
||||
[key: string]: Serializable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type SerializableOrJSHandle = Serializable | JSHandle;
|
||||
@@ -29,6 +29,7 @@ import { MouseButtonInput } from './Input';
|
||||
import { Page } from './Page';
|
||||
import { HTTPResponse } from './HTTPResponse';
|
||||
import Protocol from '../protocol';
|
||||
import { EvaluateFn, SerializableOrJSHandle } from './EvalTypes';
|
||||
|
||||
const UTILITY_WORLD_NAME = '__puppeteer_utility_world__';
|
||||
|
||||
@@ -454,16 +455,16 @@ export class Frame {
|
||||
|
||||
async $eval<ReturnType extends any>(
|
||||
selector: string,
|
||||
pageFunction: Function | string,
|
||||
...args: unknown[]
|
||||
pageFunction: EvaluateFn | string,
|
||||
...args: SerializableOrJSHandle[]
|
||||
): Promise<ReturnType> {
|
||||
return this._mainWorld.$eval<ReturnType>(selector, pageFunction, ...args);
|
||||
}
|
||||
|
||||
async $$eval<ReturnType extends any>(
|
||||
selector: string,
|
||||
pageFunction: Function | string,
|
||||
...args: unknown[]
|
||||
pageFunction: EvaluateFn | string,
|
||||
...args: SerializableOrJSHandle[]
|
||||
): Promise<ReturnType> {
|
||||
return this._mainWorld.$$eval<ReturnType>(selector, pageFunction, ...args);
|
||||
}
|
||||
|
||||
@@ -23,6 +23,11 @@ import { KeyInput } from './USKeyboardLayout';
|
||||
import { FrameManager, Frame } from './FrameManager';
|
||||
import { getQueryHandlerAndSelector } from './QueryHandler';
|
||||
import Protocol from '../protocol';
|
||||
import {
|
||||
EvaluateFn,
|
||||
SerializableOrJSHandle,
|
||||
EvaluateFnReturnType,
|
||||
} from './EvalTypes';
|
||||
|
||||
/**
|
||||
* @public
|
||||
@@ -113,11 +118,12 @@ export class JSHandle {
|
||||
* expect(await tweetHandle.evaluate(node => node.innerText)).toBe('10');
|
||||
* ```
|
||||
*/
|
||||
async evaluate<ReturnType extends any>(
|
||||
pageFunction: Function | string,
|
||||
...args: unknown[]
|
||||
): Promise<ReturnType> {
|
||||
return await this.executionContext().evaluate<ReturnType>(
|
||||
|
||||
async evaluate<T extends EvaluateFn>(
|
||||
pageFunction: T | string,
|
||||
...args: SerializableOrJSHandle[]
|
||||
): Promise<EvaluateFnReturnType<T>> {
|
||||
return await this.executionContext().evaluate<EvaluateFnReturnType<T>>(
|
||||
pageFunction,
|
||||
this,
|
||||
...args
|
||||
@@ -307,46 +313,48 @@ export class ElementHandle extends JSHandle {
|
||||
}
|
||||
|
||||
private async _scrollIntoViewIfNeeded(): Promise<void> {
|
||||
const error = await this.evaluate<Promise<string | false>>(
|
||||
async (element: HTMLElement, pageJavascriptEnabled: boolean) => {
|
||||
if (!element.isConnected) return 'Node is detached from document';
|
||||
if (element.nodeType !== Node.ELEMENT_NODE)
|
||||
return 'Node is not of type HTMLElement';
|
||||
// force-scroll if page's javascript is disabled.
|
||||
if (!pageJavascriptEnabled) {
|
||||
element.scrollIntoView({
|
||||
block: 'center',
|
||||
inline: 'center',
|
||||
// Chrome still supports behavior: instant but it's not in the spec
|
||||
// so TS shouts We don't want to make this breaking change in
|
||||
// Puppeteer yet so we'll ignore the line.
|
||||
// @ts-ignore
|
||||
behavior: 'instant',
|
||||
});
|
||||
return false;
|
||||
}
|
||||
const visibleRatio = await new Promise((resolve) => {
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
resolve(entries[0].intersectionRatio);
|
||||
observer.disconnect();
|
||||
});
|
||||
observer.observe(element);
|
||||
const error = await this.evaluate<
|
||||
(
|
||||
element: HTMLElement,
|
||||
pageJavascriptEnabled: boolean
|
||||
) => Promise<string | false>
|
||||
>(async (element, pageJavascriptEnabled) => {
|
||||
if (!element.isConnected) return 'Node is detached from document';
|
||||
if (element.nodeType !== Node.ELEMENT_NODE)
|
||||
return 'Node is not of type HTMLElement';
|
||||
// force-scroll if page's javascript is disabled.
|
||||
if (!pageJavascriptEnabled) {
|
||||
element.scrollIntoView({
|
||||
block: 'center',
|
||||
inline: 'center',
|
||||
// Chrome still supports behavior: instant but it's not in the spec
|
||||
// so TS shouts We don't want to make this breaking change in
|
||||
// Puppeteer yet so we'll ignore the line.
|
||||
// @ts-ignore
|
||||
behavior: 'instant',
|
||||
});
|
||||
if (visibleRatio !== 1.0) {
|
||||
element.scrollIntoView({
|
||||
block: 'center',
|
||||
inline: 'center',
|
||||
// Chrome still supports behavior: instant but it's not in the spec
|
||||
// so TS shouts We don't want to make this breaking change in
|
||||
// Puppeteer yet so we'll ignore the line.
|
||||
// @ts-ignore
|
||||
behavior: 'instant',
|
||||
});
|
||||
}
|
||||
return false;
|
||||
},
|
||||
this._page.isJavaScriptEnabled()
|
||||
);
|
||||
}
|
||||
const visibleRatio = await new Promise((resolve) => {
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
resolve(entries[0].intersectionRatio);
|
||||
observer.disconnect();
|
||||
});
|
||||
observer.observe(element);
|
||||
});
|
||||
if (visibleRatio !== 1.0) {
|
||||
element.scrollIntoView({
|
||||
block: 'center',
|
||||
inline: 'center',
|
||||
// Chrome still supports behavior: instant but it's not in the spec
|
||||
// so TS shouts We don't want to make this breaking change in
|
||||
// Puppeteer yet so we'll ignore the line.
|
||||
// @ts-ignore
|
||||
behavior: 'instant',
|
||||
});
|
||||
}
|
||||
return false;
|
||||
}, this._page.isJavaScriptEnabled());
|
||||
|
||||
if (error) throw new Error(error);
|
||||
}
|
||||
@@ -491,9 +499,9 @@ export class ElementHandle extends JSHandle {
|
||||
* relative to the {@link https://nodejs.org/api/process.html#process_process_cwd | current working directory}
|
||||
*/
|
||||
async uploadFile(...filePaths: string[]): Promise<void> {
|
||||
const isMultiple = await this.evaluate<boolean>(
|
||||
(element: HTMLInputElement) => element.multiple
|
||||
);
|
||||
const isMultiple = await this.evaluate<
|
||||
(element: HTMLInputElement) => boolean
|
||||
>((element) => element.multiple);
|
||||
assert(
|
||||
filePaths.length <= 1 || isMultiple,
|
||||
'Multiple file uploads only work with <input type=file multiple>'
|
||||
@@ -772,15 +780,15 @@ export class ElementHandle extends JSHandle {
|
||||
*/
|
||||
async $eval<ReturnType extends any>(
|
||||
selector: string,
|
||||
pageFunction: Function | string,
|
||||
...args: unknown[]
|
||||
pageFunction: EvaluateFn | string,
|
||||
...args: SerializableOrJSHandle[]
|
||||
): Promise<ReturnType> {
|
||||
const elementHandle = await this.$(selector);
|
||||
if (!elementHandle)
|
||||
throw new Error(
|
||||
`Error: failed to find element matching selector "${selector}"`
|
||||
);
|
||||
const result = await elementHandle.evaluate<ReturnType>(
|
||||
const result = await elementHandle.evaluate<(...args: any[]) => ReturnType>(
|
||||
pageFunction,
|
||||
...args
|
||||
);
|
||||
@@ -813,8 +821,8 @@ export class ElementHandle extends JSHandle {
|
||||
*/
|
||||
async $$eval<ReturnType extends any>(
|
||||
selector: string,
|
||||
pageFunction: Function | string,
|
||||
...args: unknown[]
|
||||
pageFunction: EvaluateFn | string,
|
||||
...args: SerializableOrJSHandle[]
|
||||
): Promise<ReturnType> {
|
||||
const defaultHandler = (element: Element, selector: string) =>
|
||||
Array.from(element.querySelectorAll(selector));
|
||||
@@ -827,7 +835,7 @@ export class ElementHandle extends JSHandle {
|
||||
queryHandler,
|
||||
updatedSelector
|
||||
);
|
||||
const result = await arrayHandle.evaluate<ReturnType>(
|
||||
const result = await arrayHandle.evaluate<(...args: any[]) => ReturnType>(
|
||||
pageFunction,
|
||||
...args
|
||||
);
|
||||
@@ -868,16 +876,18 @@ export class ElementHandle extends JSHandle {
|
||||
* Resolves to true if the element is visible in the current viewport.
|
||||
*/
|
||||
async isIntersectingViewport(): Promise<boolean> {
|
||||
return await this.evaluate<Promise<boolean>>(async (element) => {
|
||||
const visibleRatio = await new Promise((resolve) => {
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
resolve(entries[0].intersectionRatio);
|
||||
observer.disconnect();
|
||||
return await this.evaluate<(element: Element) => Promise<boolean>>(
|
||||
async (element) => {
|
||||
const visibleRatio = await new Promise((resolve) => {
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
resolve(entries[0].intersectionRatio);
|
||||
observer.disconnect();
|
||||
});
|
||||
observer.observe(element);
|
||||
});
|
||||
observer.observe(element);
|
||||
});
|
||||
return visibleRatio > 0;
|
||||
});
|
||||
return visibleRatio > 0;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ import { FileChooser } from './FileChooser';
|
||||
import { ConsoleMessage, ConsoleMessageType } from './ConsoleMessage';
|
||||
import { PuppeteerLifeCycleEvent } from './LifecycleWatcher';
|
||||
import Protocol from '../protocol';
|
||||
import { EvaluateFn, SerializableOrJSHandle } from './EvalTypes';
|
||||
|
||||
const writeFileAsync = promisify(fs.writeFile);
|
||||
|
||||
@@ -514,16 +515,16 @@ export class Page extends EventEmitter {
|
||||
|
||||
async $eval<ReturnType extends any>(
|
||||
selector: string,
|
||||
pageFunction: Function | string,
|
||||
...args: unknown[]
|
||||
pageFunction: EvaluateFn | string,
|
||||
...args: SerializableOrJSHandle[]
|
||||
): Promise<ReturnType> {
|
||||
return this.mainFrame().$eval<ReturnType>(selector, pageFunction, ...args);
|
||||
}
|
||||
|
||||
async $$eval<ReturnType extends any>(
|
||||
selector: string,
|
||||
pageFunction: Function | string,
|
||||
...args: unknown[]
|
||||
pageFunction: EvaluateFn | string,
|
||||
...args: SerializableOrJSHandle[]
|
||||
): Promise<ReturnType> {
|
||||
return this.mainFrame().$$eval<ReturnType>(selector, pageFunction, ...args);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user