feat!: type inference for evaluation types (#8547)
This PR greatly improves the types within Puppeteer: - **Almost everything** is auto-deduced. - Parameters don't need to be specified in the function. They are deduced from the spread. - Return types don't need to be specified. They are deduced from the function. (More on this below) - Selections based on tag names correctly deduce element type, similar to TypeScript's mechanism for `getElementByTagName`. - [**BREAKING CHANGE**] We've removed the ability to declare return types in type arguments for the following reasons: 1. Setting them will indubitably break auto-deduction. 2. You can just use `as ...` in TypeScript to coerce the correct type (given it makes sense). - [**BREAKING CHANGE**] `waitFor` is officially gone. To migrate to these changes, there are only four things you may need to change: - If you set a return type using the `ReturnType` type parameter, remove it and use `as ...` and `HandleFor` (if necessary). ⛔ `evaluate<ReturnType>(a: number, b: number) => {...}, a, b)` ✅ `(await evaluate(a, b) => {...}, a, b)) as ReturnType` ⛔ `evaluateHandle<ReturnType>(a: number, b: number) => {...}, a, b)` ✅ `(await evaluateHandle(a, b) => {...}, a, b)) as HandleFor<ReturnType>` - If you set any type parameters in the *parameters* of an evaluation function, remove them. ⛔ `evaluate(a: number, b: number) => {...}, a, b)` ✅ `evaluate(a, b) => {...}, a, b)` - If you set any type parameters in the method's declaration, remove them. ⛔ `evaluate<(a: number, b: number) => void>((a, b) => {...}, a, b)` ✅ `evaluate(a, b) => {...}, a, b)`
This commit is contained in:
parent
da269c3f32
commit
26c3acbb07
@ -72,7 +72,7 @@ export * from './common/Tracing.js';
|
||||
export * from './common/NetworkManager.js';
|
||||
export * from './common/WebWorker.js';
|
||||
export * from './common/USKeyboardLayout.js';
|
||||
export * from './common/EvalTypes.js';
|
||||
export * from './common/types.js';
|
||||
export * from './common/PDFOptions.js';
|
||||
export * from './common/TimeoutSettings.js';
|
||||
export * from './common/LifecycleWatcher.js';
|
||||
|
@ -141,7 +141,7 @@ const queryAll = async (
|
||||
const queryAllArray = async (
|
||||
element: ElementHandle,
|
||||
selector: string
|
||||
): Promise<JSHandle> => {
|
||||
): Promise<JSHandle<Element[]>> => {
|
||||
const elementHandles = await queryAll(element, selector);
|
||||
const exeCtx = element.executionContext();
|
||||
const jsHandle = exeCtx.evaluateHandle((...elements) => {
|
||||
@ -153,7 +153,7 @@ const queryAllArray = async (
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export const _ariaHandler: InternalQueryHandler = {
|
||||
export const ariaHandler: InternalQueryHandler = {
|
||||
queryOne,
|
||||
waitFor,
|
||||
queryAll,
|
||||
|
@ -33,8 +33,8 @@ export {ConnectionTransport, ProtocolMapping};
|
||||
* @public
|
||||
*/
|
||||
export interface ConnectionCallback {
|
||||
resolve: Function;
|
||||
reject: Function;
|
||||
resolve(args: unknown): void;
|
||||
reject(args: unknown): void;
|
||||
error: ProtocolError;
|
||||
method: string;
|
||||
}
|
||||
|
@ -18,16 +18,14 @@ import {Protocol} from 'devtools-protocol';
|
||||
import {assert} from './assert.js';
|
||||
import {CDPSession} from './Connection.js';
|
||||
import {TimeoutError} from './Errors.js';
|
||||
import {
|
||||
EvaluateFn,
|
||||
EvaluateFnReturnType,
|
||||
EvaluateHandleFn,
|
||||
SerializableOrJSHandle,
|
||||
UnwrapPromiseLike,
|
||||
WrapElementHandle,
|
||||
} from './EvalTypes.js';
|
||||
import {ExecutionContext} from './ExecutionContext.js';
|
||||
import {Frame, FrameManager} from './FrameManager.js';
|
||||
import {MouseButton} from './Input.js';
|
||||
import {ElementHandle, JSHandle} from './JSHandle.js';
|
||||
import {LifecycleWatcher, PuppeteerLifeCycleEvent} from './LifecycleWatcher.js';
|
||||
import {_getQueryHandlerAndSelector} from './QueryHandler.js';
|
||||
import {TimeoutSettings} from './TimeoutSettings.js';
|
||||
import {EvaluateFunc, EvaluateParams, HandleFor} from './types.js';
|
||||
import {
|
||||
debugError,
|
||||
isNumber,
|
||||
@ -35,11 +33,6 @@ import {
|
||||
makePredicateString,
|
||||
pageBindingInitString,
|
||||
} from './util.js';
|
||||
import {MouseButton} from './Input.js';
|
||||
import {ElementHandle, JSHandle} from './JSHandle.js';
|
||||
import {LifecycleWatcher, PuppeteerLifeCycleEvent} from './LifecycleWatcher.js';
|
||||
import {_getQueryHandlerAndSelector} from './QueryHandler.js';
|
||||
import {TimeoutSettings} from './TimeoutSettings.js';
|
||||
|
||||
// predicateQueryHandler and checkWaitForOptions are declared here so that
|
||||
// TypeScript knows about them when used in the predicate function below.
|
||||
@ -184,30 +177,45 @@ export class DOMWorld {
|
||||
return this.#contextPromise;
|
||||
}
|
||||
|
||||
async evaluateHandle<HandlerType extends JSHandle = JSHandle>(
|
||||
pageFunction: EvaluateHandleFn,
|
||||
...args: SerializableOrJSHandle[]
|
||||
): Promise<HandlerType> {
|
||||
async evaluateHandle<
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
|
||||
>(
|
||||
pageFunction: Func | string,
|
||||
...args: EvaluateParams<Params>
|
||||
): Promise<HandleFor<Awaited<ReturnType<Func>>>> {
|
||||
const context = await this.executionContext();
|
||||
return context.evaluateHandle(pageFunction, ...args);
|
||||
}
|
||||
|
||||
async evaluate<T extends EvaluateFn>(
|
||||
pageFunction: T,
|
||||
...args: SerializableOrJSHandle[]
|
||||
): Promise<UnwrapPromiseLike<EvaluateFnReturnType<T>>> {
|
||||
async evaluate<
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
|
||||
>(
|
||||
pageFunction: Func | string,
|
||||
...args: EvaluateParams<Params>
|
||||
): Promise<Awaited<ReturnType<Func>>> {
|
||||
const context = await this.executionContext();
|
||||
return context.evaluate<UnwrapPromiseLike<EvaluateFnReturnType<T>>>(
|
||||
pageFunction,
|
||||
...args
|
||||
);
|
||||
return context.evaluate(pageFunction, ...args);
|
||||
}
|
||||
|
||||
async $<T extends Element = Element>(
|
||||
selector: string
|
||||
): Promise<ElementHandle<T> | null> {
|
||||
async $<Selector extends keyof HTMLElementTagNameMap>(
|
||||
selector: Selector
|
||||
): Promise<ElementHandle<HTMLElementTagNameMap[Selector]> | null>;
|
||||
async $(selector: string): Promise<ElementHandle | null>;
|
||||
async $(selector: string): Promise<ElementHandle | null> {
|
||||
const document = await this._document();
|
||||
const value = await document.$<T>(selector);
|
||||
const value = await document.$(selector);
|
||||
return value;
|
||||
}
|
||||
|
||||
async $$<Selector extends keyof HTMLElementTagNameMap>(
|
||||
selector: Selector
|
||||
): Promise<ElementHandle<HTMLElementTagNameMap[Selector]>[]>;
|
||||
async $$(selector: string): Promise<ElementHandle[]>;
|
||||
async $$(selector: string): Promise<ElementHandle[]> {
|
||||
const document = await this._document();
|
||||
const value = await document.$$(selector);
|
||||
return value;
|
||||
}
|
||||
|
||||
@ -235,40 +243,74 @@ export class DOMWorld {
|
||||
return value;
|
||||
}
|
||||
|
||||
async $eval<ReturnType>(
|
||||
async $eval<
|
||||
Selector extends keyof HTMLElementTagNameMap,
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<
|
||||
[HTMLElementTagNameMap[Selector], ...Params]
|
||||
> = EvaluateFunc<[HTMLElementTagNameMap[Selector], ...Params]>
|
||||
>(
|
||||
selector: Selector,
|
||||
pageFunction: Func | string,
|
||||
...args: EvaluateParams<Params>
|
||||
): Promise<Awaited<ReturnType<Func>>>;
|
||||
async $eval<
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<[Element, ...Params]> = EvaluateFunc<
|
||||
[Element, ...Params]
|
||||
>
|
||||
>(
|
||||
selector: string,
|
||||
pageFunction: (
|
||||
element: Element,
|
||||
...args: unknown[]
|
||||
) => ReturnType | Promise<ReturnType>,
|
||||
...args: SerializableOrJSHandle[]
|
||||
): Promise<WrapElementHandle<ReturnType>> {
|
||||
pageFunction: Func | string,
|
||||
...args: EvaluateParams<Params>
|
||||
): Promise<Awaited<ReturnType<Func>>>;
|
||||
async $eval<
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<[Element, ...Params]> = EvaluateFunc<
|
||||
[Element, ...Params]
|
||||
>
|
||||
>(
|
||||
selector: string,
|
||||
pageFunction: Func | string,
|
||||
...args: EvaluateParams<Params>
|
||||
): Promise<Awaited<ReturnType<Func>>> {
|
||||
const document = await this._document();
|
||||
return document.$eval<ReturnType>(selector, pageFunction, ...args);
|
||||
return document.$eval(selector, pageFunction, ...args);
|
||||
}
|
||||
|
||||
async $$eval<ReturnType>(
|
||||
async $$eval<
|
||||
Selector extends keyof HTMLElementTagNameMap,
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<
|
||||
[HTMLElementTagNameMap[Selector][], ...Params]
|
||||
> = EvaluateFunc<[HTMLElementTagNameMap[Selector][], ...Params]>
|
||||
>(
|
||||
selector: Selector,
|
||||
pageFunction: Func | string,
|
||||
...args: EvaluateParams<Params>
|
||||
): Promise<Awaited<ReturnType<Func>>>;
|
||||
async $$eval<
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<[Element[], ...Params]> = EvaluateFunc<
|
||||
[Element[], ...Params]
|
||||
>
|
||||
>(
|
||||
selector: string,
|
||||
pageFunction: (
|
||||
elements: Element[],
|
||||
...args: unknown[]
|
||||
) => ReturnType | Promise<ReturnType>,
|
||||
...args: SerializableOrJSHandle[]
|
||||
): Promise<WrapElementHandle<ReturnType>> {
|
||||
pageFunction: Func | string,
|
||||
...args: EvaluateParams<Params>
|
||||
): Promise<Awaited<ReturnType<Func>>>;
|
||||
async $$eval<
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<[Element[], ...Params]> = EvaluateFunc<
|
||||
[Element[], ...Params]
|
||||
>
|
||||
>(
|
||||
selector: string,
|
||||
pageFunction: Func | string,
|
||||
...args: EvaluateParams<Params>
|
||||
): Promise<Awaited<ReturnType<Func>>> {
|
||||
const document = await this._document();
|
||||
const value = await document.$$eval<ReturnType>(
|
||||
selector,
|
||||
pageFunction,
|
||||
...args
|
||||
);
|
||||
return value;
|
||||
}
|
||||
|
||||
async $$<T extends Element = Element>(
|
||||
selector: string
|
||||
): Promise<Array<ElementHandle<T>>> {
|
||||
const document = await this._document();
|
||||
const value = await document.$$<T>(selector);
|
||||
const value = await document.$$eval(selector, pageFunction, ...args);
|
||||
return value;
|
||||
}
|
||||
|
||||
@ -298,7 +340,7 @@ export class DOMWorld {
|
||||
} = options;
|
||||
// We rely upon the fact that document.open() will reset frame lifecycle with "init"
|
||||
// lifecycle event. @see https://crrev.com/608658
|
||||
await this.evaluate<(x: string) => void>(html => {
|
||||
await this.evaluate(html => {
|
||||
document.open();
|
||||
document.write(html);
|
||||
document.close();
|
||||
@ -536,7 +578,6 @@ export class DOMWorld {
|
||||
|
||||
async function addStyleContent(content: string): Promise<HTMLElement> {
|
||||
const style = document.createElement('style');
|
||||
style.type = 'text/css';
|
||||
style.appendChild(document.createTextNode(content));
|
||||
const promise = new Promise((res, rej) => {
|
||||
style.onload = res;
|
||||
@ -598,6 +639,14 @@ export class DOMWorld {
|
||||
await handle.dispose();
|
||||
}
|
||||
|
||||
async waitForSelector<Selector extends keyof HTMLElementTagNameMap>(
|
||||
selector: Selector,
|
||||
options: WaitForSelectorOptions
|
||||
): Promise<ElementHandle<HTMLElementTagNameMap[Selector]> | null>;
|
||||
async waitForSelector(
|
||||
selector: string,
|
||||
options: WaitForSelectorOptions
|
||||
): Promise<ElementHandle | null>;
|
||||
async waitForSelector(
|
||||
selector: string,
|
||||
options: WaitForSelectorOptions
|
||||
@ -825,7 +874,7 @@ export class DOMWorld {
|
||||
waitForFunction(
|
||||
pageFunction: Function | string,
|
||||
options: {polling?: string | number; timeout?: number} = {},
|
||||
...args: SerializableOrJSHandle[]
|
||||
...args: unknown[]
|
||||
): Promise<JSHandle> {
|
||||
const {polling = 'raf', timeout = this.#timeoutSettings.timeout()} =
|
||||
options;
|
||||
@ -860,7 +909,7 @@ export interface WaitTaskOptions {
|
||||
polling: string | number;
|
||||
timeout: number;
|
||||
binding?: PageBinding;
|
||||
args: SerializableOrJSHandle[];
|
||||
args: unknown[];
|
||||
root?: ElementHandle;
|
||||
}
|
||||
|
||||
@ -871,11 +920,11 @@ const noop = (): void => {};
|
||||
*/
|
||||
export class WaitTask {
|
||||
#domWorld: DOMWorld;
|
||||
#polling: string | number;
|
||||
#polling: 'raf' | 'mutation' | number;
|
||||
#timeout: number;
|
||||
#predicateBody: string;
|
||||
#predicateAcceptsContextElement: boolean;
|
||||
#args: SerializableOrJSHandle[];
|
||||
#args: unknown[];
|
||||
#binding?: PageBinding;
|
||||
#runCount = 0;
|
||||
#resolve: (x: JSHandle) => void = noop;
|
||||
|
@ -1,83 +0,0 @@
|
||||
/**
|
||||
* 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, ElementHandle} from './JSHandle.js';
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type EvaluateFn<T = any, U = any, V = any> =
|
||||
| string
|
||||
| ((arg1: T, ...args: U[]) => V);
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type UnwrapPromiseLike<T> = T extends PromiseLike<infer U> ? U : T;
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type EvaluateFnReturnType<T extends EvaluateFn> = T extends (
|
||||
...args: any[]
|
||||
) => infer R
|
||||
? R
|
||||
: any;
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type EvaluateHandleFn = string | ((...args: any[]) => any);
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type Serializable =
|
||||
| number
|
||||
| string
|
||||
| boolean
|
||||
| null
|
||||
| bigint
|
||||
| JSONArray
|
||||
| JSONObject;
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type JSONArray = readonly Serializable[];
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface JSONObject {
|
||||
[key: string]: Serializable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
@ -18,10 +18,10 @@ import {Protocol} from 'devtools-protocol';
|
||||
import {assert} from './assert.js';
|
||||
import {CDPSession} from './Connection.js';
|
||||
import {DOMWorld} from './DOMWorld.js';
|
||||
import {EvaluateHandleFn, SerializableOrJSHandle} from './EvalTypes.js';
|
||||
import {EvaluateFunc, HandleFor, EvaluateParams} from './types.js';
|
||||
import {Frame} from './FrameManager.js';
|
||||
import {getExceptionMessage, isString, valueFromRemoteObject} from './util.js';
|
||||
import {ElementHandle, JSHandle, _createJSHandle} from './JSHandle.js';
|
||||
import {getExceptionMessage, isString, valueFromRemoteObject} from './util.js';
|
||||
|
||||
/**
|
||||
* @public
|
||||
@ -134,11 +134,14 @@ export class ExecutionContext {
|
||||
*
|
||||
* @returns A promise that resolves to the return value of the given function.
|
||||
*/
|
||||
async evaluate<ReturnType>(
|
||||
pageFunction: Function | string,
|
||||
...args: unknown[]
|
||||
): Promise<ReturnType> {
|
||||
return await this.#evaluate<ReturnType>(true, pageFunction, ...args);
|
||||
async evaluate<
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
|
||||
>(
|
||||
pageFunction: Func | string,
|
||||
...args: EvaluateParams<Params>
|
||||
): Promise<Awaited<ReturnType<Func>>> {
|
||||
return await this.#evaluate(true, pageFunction, ...args);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -183,18 +186,40 @@ export class ExecutionContext {
|
||||
* @returns A promise that resolves to the return value of the given function
|
||||
* as an in-page object (a {@link JSHandle}).
|
||||
*/
|
||||
async evaluateHandle<HandleType extends JSHandle | ElementHandle = JSHandle>(
|
||||
pageFunction: EvaluateHandleFn,
|
||||
...args: SerializableOrJSHandle[]
|
||||
): Promise<HandleType> {
|
||||
return this.#evaluate<HandleType>(false, pageFunction, ...args);
|
||||
async evaluateHandle<
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
|
||||
>(
|
||||
pageFunction: Func | string,
|
||||
...args: EvaluateParams<Params>
|
||||
): Promise<HandleFor<Awaited<ReturnType<Func>>>> {
|
||||
return this.#evaluate(false, pageFunction, ...args);
|
||||
}
|
||||
|
||||
async #evaluate<ReturnType>(
|
||||
async #evaluate<
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
|
||||
>(
|
||||
returnByValue: true,
|
||||
pageFunction: Func | string,
|
||||
...args: EvaluateParams<Params>
|
||||
): Promise<Awaited<ReturnType<Func>>>;
|
||||
async #evaluate<
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
|
||||
>(
|
||||
returnByValue: false,
|
||||
pageFunction: Func | string,
|
||||
...args: EvaluateParams<Params>
|
||||
): Promise<HandleFor<Awaited<ReturnType<Func>>>>;
|
||||
async #evaluate<
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
|
||||
>(
|
||||
returnByValue: boolean,
|
||||
pageFunction: Function | string,
|
||||
...args: unknown[]
|
||||
): Promise<ReturnType> {
|
||||
pageFunction: Func | string,
|
||||
...args: EvaluateParams<Params>
|
||||
): Promise<HandleFor<Awaited<ReturnType<Func>>> | Awaited<ReturnType<Func>>> {
|
||||
const suffix = `//# sourceURL=${EVALUATION_SCRIPT_URL}`;
|
||||
|
||||
if (isString(pageFunction)) {
|
||||
@ -365,7 +390,9 @@ export class ExecutionContext {
|
||||
*
|
||||
* @returns A handle to an array of objects with the given prototype.
|
||||
*/
|
||||
async queryObjects(prototypeHandle: JSHandle): Promise<JSHandle> {
|
||||
async queryObjects<Prototype>(
|
||||
prototypeHandle: JSHandle<Prototype>
|
||||
): Promise<HandleFor<Prototype[]>> {
|
||||
assert(!prototypeHandle._disposed, 'Prototype JSHandle is disposed!');
|
||||
assert(
|
||||
prototypeHandle._remoteObject.objectId,
|
||||
@ -374,7 +401,7 @@ export class ExecutionContext {
|
||||
const response = await this._client.send('Runtime.queryObjects', {
|
||||
prototypeObjectId: prototypeHandle._remoteObject.objectId,
|
||||
});
|
||||
return _createJSHandle(this, response.objects);
|
||||
return _createJSHandle(this, response.objects) as HandleFor<Prototype[]>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -37,7 +37,7 @@ import {assert} from './assert.js';
|
||||
* @public
|
||||
*/
|
||||
export class FileChooser {
|
||||
#element: ElementHandle;
|
||||
#element: ElementHandle<HTMLInputElement>;
|
||||
#multiple: boolean;
|
||||
#handled = false;
|
||||
|
||||
@ -45,7 +45,7 @@ export class FileChooser {
|
||||
* @internal
|
||||
*/
|
||||
constructor(
|
||||
element: ElementHandle,
|
||||
element: ElementHandle<HTMLInputElement>,
|
||||
event: Protocol.Page.FileChooserOpenedEvent
|
||||
) {
|
||||
this.#element = element;
|
||||
|
@ -18,27 +18,19 @@ import {Protocol} from 'devtools-protocol';
|
||||
import {assert} from './assert.js';
|
||||
import {CDPSession, Connection} from './Connection.js';
|
||||
import {DOMWorld, WaitForSelectorOptions} from './DOMWorld.js';
|
||||
import {
|
||||
EvaluateFn,
|
||||
EvaluateFnReturnType,
|
||||
EvaluateHandleFn,
|
||||
SerializableOrJSHandle,
|
||||
UnwrapPromiseLike,
|
||||
WrapElementHandle,
|
||||
} from './EvalTypes.js';
|
||||
import {EventEmitter} from './EventEmitter.js';
|
||||
import {EVALUATION_SCRIPT_URL, ExecutionContext} from './ExecutionContext.js';
|
||||
import {HTTPResponse} from './HTTPResponse.js';
|
||||
import {MouseButton} from './Input.js';
|
||||
import {ElementHandle, JSHandle} from './JSHandle.js';
|
||||
import {ElementHandle} from './JSHandle.js';
|
||||
import {LifecycleWatcher, PuppeteerLifeCycleEvent} from './LifecycleWatcher.js';
|
||||
import {NetworkManager} from './NetworkManager.js';
|
||||
import {Page} from './Page.js';
|
||||
import {TimeoutSettings} from './TimeoutSettings.js';
|
||||
import {debugError, isErrorLike, isNumber, isString} from './util.js';
|
||||
import {EvaluateFunc, EvaluateParams, HandleFor} from './types.js';
|
||||
import {debugError, isErrorLike} from './util.js';
|
||||
|
||||
const UTILITY_WORLD_NAME = '__puppeteer_utility_world__';
|
||||
const xPathPattern = /^\(\/\/[^\)]+\)|^\/\//;
|
||||
|
||||
/**
|
||||
* We use symbols to prevent external parties listening to these events.
|
||||
@ -892,11 +884,14 @@ export class Frame {
|
||||
* @param pageFunction - a function that is run within the frame
|
||||
* @param args - arguments to be passed to the pageFunction
|
||||
*/
|
||||
async evaluateHandle<HandlerType extends JSHandle = JSHandle>(
|
||||
pageFunction: EvaluateHandleFn,
|
||||
...args: SerializableOrJSHandle[]
|
||||
): Promise<HandlerType> {
|
||||
return this._mainWorld.evaluateHandle<HandlerType>(pageFunction, ...args);
|
||||
async evaluateHandle<
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
|
||||
>(
|
||||
pageFunction: Func | string,
|
||||
...args: EvaluateParams<Params>
|
||||
): Promise<HandleFor<Awaited<ReturnType<Func>>>> {
|
||||
return this._mainWorld.evaluateHandle(pageFunction, ...args);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -908,11 +903,14 @@ export class Frame {
|
||||
* @param pageFunction - a function that is run within the frame
|
||||
* @param args - arguments to be passed to the pageFunction
|
||||
*/
|
||||
async evaluate<T extends EvaluateFn>(
|
||||
pageFunction: T,
|
||||
...args: SerializableOrJSHandle[]
|
||||
): Promise<UnwrapPromiseLike<EvaluateFnReturnType<T>>> {
|
||||
return this._mainWorld.evaluate<T>(pageFunction, ...args);
|
||||
async evaluate<
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
|
||||
>(
|
||||
pageFunction: Func | string,
|
||||
...args: EvaluateParams<Params>
|
||||
): Promise<Awaited<ReturnType<Func>>> {
|
||||
return this._mainWorld.evaluate(pageFunction, ...args);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -922,10 +920,26 @@ export class Frame {
|
||||
* @returns A promise which resolves to an `ElementHandle` pointing at the
|
||||
* element, or `null` if it was not found.
|
||||
*/
|
||||
async $<T extends Element = Element>(
|
||||
selector: string
|
||||
): Promise<ElementHandle<T> | null> {
|
||||
return this._mainWorld.$<T>(selector);
|
||||
async $<Selector extends keyof HTMLElementTagNameMap>(
|
||||
selector: Selector
|
||||
): Promise<ElementHandle<HTMLElementTagNameMap[Selector]> | null>;
|
||||
async $(selector: string): Promise<ElementHandle | null>;
|
||||
async $(selector: string): Promise<ElementHandle | null> {
|
||||
return this._mainWorld.$(selector);
|
||||
}
|
||||
|
||||
/**
|
||||
* This runs `document.querySelectorAll` in the frame and returns the result.
|
||||
*
|
||||
* @param selector - a selector to search for
|
||||
* @returns An array of element handles pointing to the found frame elements.
|
||||
*/
|
||||
async $$<Selector extends keyof HTMLElementTagNameMap>(
|
||||
selector: Selector
|
||||
): Promise<ElementHandle<HTMLElementTagNameMap[Selector]>[]>;
|
||||
async $$(selector: string): Promise<ElementHandle[]>;
|
||||
async $$(selector: string): Promise<ElementHandle[]> {
|
||||
return this._mainWorld.$$(selector);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -956,15 +970,38 @@ export class Frame {
|
||||
* @param pageFunction - the function to be evaluated in the frame's context
|
||||
* @param args - additional arguments to pass to `pageFunction`
|
||||
*/
|
||||
async $eval<ReturnType>(
|
||||
async $eval<
|
||||
Selector extends keyof HTMLElementTagNameMap,
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<
|
||||
[HTMLElementTagNameMap[Selector], ...Params]
|
||||
> = EvaluateFunc<[HTMLElementTagNameMap[Selector], ...Params]>
|
||||
>(
|
||||
selector: Selector,
|
||||
pageFunction: Func | string,
|
||||
...args: EvaluateParams<Params>
|
||||
): Promise<Awaited<ReturnType<Func>>>;
|
||||
async $eval<
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<[Element, ...Params]> = EvaluateFunc<
|
||||
[Element, ...Params]
|
||||
>
|
||||
>(
|
||||
selector: string,
|
||||
pageFunction: (
|
||||
element: Element,
|
||||
...args: unknown[]
|
||||
) => ReturnType | Promise<ReturnType>,
|
||||
...args: SerializableOrJSHandle[]
|
||||
): Promise<WrapElementHandle<ReturnType>> {
|
||||
return this._mainWorld.$eval<ReturnType>(selector, pageFunction, ...args);
|
||||
pageFunction: Func | string,
|
||||
...args: EvaluateParams<Params>
|
||||
): Promise<Awaited<ReturnType<Func>>>;
|
||||
async $eval<
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<[Element, ...Params]> = EvaluateFunc<
|
||||
[Element, ...Params]
|
||||
>
|
||||
>(
|
||||
selector: string,
|
||||
pageFunction: Func | string,
|
||||
...args: EvaluateParams<Params>
|
||||
): Promise<Awaited<ReturnType<Func>>> {
|
||||
return this._mainWorld.$eval(selector, pageFunction, ...args);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -986,27 +1023,38 @@ export class Frame {
|
||||
* @param pageFunction - the function to be evaluated in the frame's context
|
||||
* @param args - additional arguments to pass to `pageFunction`
|
||||
*/
|
||||
async $$eval<ReturnType>(
|
||||
async $$eval<
|
||||
Selector extends keyof HTMLElementTagNameMap,
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<
|
||||
[HTMLElementTagNameMap[Selector][], ...Params]
|
||||
> = EvaluateFunc<[HTMLElementTagNameMap[Selector][], ...Params]>
|
||||
>(
|
||||
selector: Selector,
|
||||
pageFunction: Func | string,
|
||||
...args: EvaluateParams<Params>
|
||||
): Promise<Awaited<ReturnType<Func>>>;
|
||||
async $$eval<
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<[Element[], ...Params]> = EvaluateFunc<
|
||||
[Element[], ...Params]
|
||||
>
|
||||
>(
|
||||
selector: string,
|
||||
pageFunction: (
|
||||
elements: Element[],
|
||||
...args: unknown[]
|
||||
) => ReturnType | Promise<ReturnType>,
|
||||
...args: SerializableOrJSHandle[]
|
||||
): Promise<WrapElementHandle<ReturnType>> {
|
||||
return this._mainWorld.$$eval<ReturnType>(selector, pageFunction, ...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* This runs `document.querySelectorAll` in the frame and returns the result.
|
||||
*
|
||||
* @param selector - a selector to search for
|
||||
* @returns An array of element handles pointing to the found frame elements.
|
||||
*/
|
||||
async $$<T extends Element = Element>(
|
||||
selector: string
|
||||
): Promise<Array<ElementHandle<T>>> {
|
||||
return this._mainWorld.$$<T>(selector);
|
||||
pageFunction: Func | string,
|
||||
...args: EvaluateParams<Params>
|
||||
): Promise<Awaited<ReturnType<Func>>>;
|
||||
async $$eval<
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<[Element[], ...Params]> = EvaluateFunc<
|
||||
[Element[], ...Params]
|
||||
>
|
||||
>(
|
||||
selector: string,
|
||||
pageFunction: Func | string,
|
||||
...args: EvaluateParams<Params>
|
||||
): Promise<Awaited<ReturnType<Func>>> {
|
||||
return this._mainWorld.$$eval(selector, pageFunction, ...args);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1238,66 +1286,6 @@ export class Frame {
|
||||
return this._mainWorld.type(selector, text, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @remarks
|
||||
*
|
||||
* This method behaves differently depending on the first parameter. If it's a
|
||||
* `string`, it will be treated as a `selector` or `xpath` (if the string
|
||||
* starts with `//`). This method then is a shortcut for
|
||||
* {@link Frame.waitForSelector} or {@link Frame.waitForXPath}.
|
||||
*
|
||||
* If the first argument is a function this method is a shortcut for
|
||||
* {@link Frame.waitForFunction}.
|
||||
*
|
||||
* If the first argument is a `number`, it's treated as a timeout in
|
||||
* milliseconds and the method returns a promise which resolves after the
|
||||
* timeout.
|
||||
*
|
||||
* @param selectorOrFunctionOrTimeout - a selector, predicate or timeout to
|
||||
* wait for.
|
||||
* @param options - optional waiting parameters.
|
||||
* @param args - arguments to pass to `pageFunction`.
|
||||
*
|
||||
* @deprecated Don't use this method directly. Instead use the more explicit
|
||||
* methods available: {@link Frame.waitForSelector},
|
||||
* {@link Frame.waitForXPath}, {@link Frame.waitForFunction} or
|
||||
* {@link Frame.waitForTimeout}.
|
||||
*/
|
||||
waitFor(
|
||||
selectorOrFunctionOrTimeout: string | number | Function,
|
||||
options: Record<string, unknown> = {},
|
||||
...args: SerializableOrJSHandle[]
|
||||
): Promise<JSHandle | null> {
|
||||
console.warn(
|
||||
'waitFor is deprecated and will be removed in a future release. See https://github.com/puppeteer/puppeteer/issues/6214 for details and how to migrate your code.'
|
||||
);
|
||||
|
||||
if (isString(selectorOrFunctionOrTimeout)) {
|
||||
const string = selectorOrFunctionOrTimeout;
|
||||
if (xPathPattern.test(string)) {
|
||||
return this.waitForXPath(string, options);
|
||||
}
|
||||
return this.waitForSelector(string, options);
|
||||
}
|
||||
if (isNumber(selectorOrFunctionOrTimeout)) {
|
||||
return new Promise(fulfill => {
|
||||
return setTimeout(fulfill, selectorOrFunctionOrTimeout);
|
||||
});
|
||||
}
|
||||
if (typeof selectorOrFunctionOrTimeout === 'function') {
|
||||
return this.waitForFunction(
|
||||
selectorOrFunctionOrTimeout,
|
||||
options,
|
||||
...args
|
||||
);
|
||||
}
|
||||
return Promise.reject(
|
||||
new Error(
|
||||
'Unsupported target type: ' + typeof selectorOrFunctionOrTimeout
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Causes your script to wait for the given number of milliseconds.
|
||||
*
|
||||
@ -1357,6 +1345,14 @@ export class Frame {
|
||||
* @returns a promise which resolves when an element matching the selector
|
||||
* string is added to the DOM.
|
||||
*/
|
||||
async waitForSelector<Selector extends keyof HTMLElementTagNameMap>(
|
||||
selector: Selector,
|
||||
options?: WaitForSelectorOptions
|
||||
): Promise<ElementHandle<HTMLElementTagNameMap[Selector]> | null>;
|
||||
async waitForSelector(
|
||||
selector: string,
|
||||
options?: WaitForSelectorOptions
|
||||
): Promise<ElementHandle | null>;
|
||||
async waitForSelector(
|
||||
selector: string,
|
||||
options: WaitForSelectorOptions = {}
|
||||
@ -1438,12 +1434,20 @@ export class Frame {
|
||||
* @param args - arguments to pass to the `pageFunction`.
|
||||
* @returns the promise which resolve when the `pageFunction` returns a truthy value.
|
||||
*/
|
||||
waitForFunction(
|
||||
pageFunction: Function | string,
|
||||
waitForFunction<
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
|
||||
>(
|
||||
pageFunction: Func | string,
|
||||
options: FrameWaitForFunctionOptions = {},
|
||||
...args: SerializableOrJSHandle[]
|
||||
): Promise<JSHandle> {
|
||||
return this._mainWorld.waitForFunction(pageFunction, options, ...args);
|
||||
...args: EvaluateParams<Params>
|
||||
): Promise<HandleFor<Awaited<ReturnType<Func>>>> {
|
||||
// TODO: Fix when NodeHandle has been added.
|
||||
return this._mainWorld.waitForFunction(
|
||||
pageFunction,
|
||||
options,
|
||||
...args
|
||||
) as Promise<HandleFor<Awaited<ReturnType<Func>>>>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -17,14 +17,7 @@
|
||||
import {Protocol} from 'devtools-protocol';
|
||||
import {assert} from './assert.js';
|
||||
import {CDPSession} from './Connection.js';
|
||||
import {
|
||||
EvaluateFn,
|
||||
EvaluateFnReturnType,
|
||||
EvaluateHandleFn,
|
||||
SerializableOrJSHandle,
|
||||
UnwrapPromiseLike,
|
||||
WrapElementHandle,
|
||||
} from './EvalTypes.js';
|
||||
import {EvaluateFunc, EvaluateParams, HandleFor, HandleOr} from './types.js';
|
||||
import {ExecutionContext} from './ExecutionContext.js';
|
||||
import {Frame, FrameManager} from './FrameManager.js';
|
||||
import {MouseButton} from './Input.js';
|
||||
@ -37,6 +30,7 @@ import {
|
||||
releaseObject,
|
||||
valueFromRemoteObject,
|
||||
} from './util.js';
|
||||
import {WaitForSelectorOptions} from './DOMWorld.js';
|
||||
|
||||
/**
|
||||
* @public
|
||||
@ -70,7 +64,7 @@ export interface BoundingBox extends Point {
|
||||
export function _createJSHandle(
|
||||
context: ExecutionContext,
|
||||
remoteObject: Protocol.Runtime.RemoteObject
|
||||
): JSHandle {
|
||||
): JSHandle | ElementHandle {
|
||||
const frame = context.frame();
|
||||
if (remoteObject.subtype === 'node' && frame) {
|
||||
const frameManager = frame._frameManager;
|
||||
@ -114,7 +108,7 @@ const applyOffsetsToQuad = (
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export class JSHandle<HandleObjectType = unknown> {
|
||||
export class JSHandle<T = unknown> {
|
||||
#client: CDPSession;
|
||||
#disposed = false;
|
||||
#context: ExecutionContext;
|
||||
@ -179,13 +173,14 @@ export class JSHandle<HandleObjectType = unknown> {
|
||||
* ```
|
||||
*/
|
||||
|
||||
async evaluate<T extends EvaluateFn<HandleObjectType>>(
|
||||
pageFunction: T | string,
|
||||
...args: SerializableOrJSHandle[]
|
||||
): Promise<UnwrapPromiseLike<EvaluateFnReturnType<T>>> {
|
||||
return await this.executionContext().evaluate<
|
||||
UnwrapPromiseLike<EvaluateFnReturnType<T>>
|
||||
>(pageFunction, this, ...args);
|
||||
async evaluate<
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<[T, ...Params]> = EvaluateFunc<[T, ...Params]>
|
||||
>(
|
||||
pageFunction: Func | string,
|
||||
...args: EvaluateParams<Params>
|
||||
): Promise<Awaited<ReturnType<Func>>> {
|
||||
return await this.executionContext().evaluate(pageFunction, this, ...args);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -203,10 +198,13 @@ export class JSHandle<HandleObjectType = unknown> {
|
||||
*
|
||||
* See {@link Page.evaluateHandle} for more details.
|
||||
*/
|
||||
async evaluateHandle<HandleType extends JSHandle = JSHandle>(
|
||||
pageFunction: EvaluateHandleFn,
|
||||
...args: SerializableOrJSHandle[]
|
||||
): Promise<HandleType> {
|
||||
async evaluateHandle<
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<[T, ...Params]> = EvaluateFunc<[T, ...Params]>
|
||||
>(
|
||||
pageFunction: Func,
|
||||
...args: EvaluateParams<Params>
|
||||
): Promise<HandleFor<Awaited<ReturnType<Func>>>> {
|
||||
return await this.executionContext().evaluateHandle(
|
||||
pageFunction,
|
||||
this,
|
||||
@ -214,22 +212,19 @@ export class JSHandle<HandleObjectType = unknown> {
|
||||
);
|
||||
}
|
||||
|
||||
/** Fetches a single property from the referenced object.
|
||||
/**
|
||||
* Fetches a single property from the referenced object.
|
||||
*/
|
||||
async getProperty(propertyName: string): Promise<JSHandle> {
|
||||
const objectHandle = await this.evaluateHandle(
|
||||
(object: Element, propertyName: keyof Element) => {
|
||||
const result: Record<string, unknown> = {__proto__: null};
|
||||
result[propertyName] = object[propertyName];
|
||||
return result;
|
||||
},
|
||||
propertyName
|
||||
);
|
||||
const properties = await objectHandle.getProperties();
|
||||
const result = properties.get(propertyName);
|
||||
assert(result instanceof JSHandle);
|
||||
await objectHandle.dispose();
|
||||
return result;
|
||||
async getProperty<K extends keyof T>(
|
||||
propertyName: HandleOr<K>
|
||||
): Promise<HandleFor<T[K]>>;
|
||||
async getProperty(propertyName: string): Promise<JSHandle<unknown>>;
|
||||
async getProperty<K extends keyof T>(
|
||||
propertyName: HandleOr<K>
|
||||
): Promise<HandleFor<T[K]>> {
|
||||
return await this.evaluateHandle((object, propertyName) => {
|
||||
return object[propertyName];
|
||||
}, propertyName);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -412,13 +407,17 @@ export class ElementHandle<
|
||||
* (30 seconds). Pass `0` to disable timeout. The default value can be changed
|
||||
* by using the {@link Page.setDefaultTimeout} method.
|
||||
*/
|
||||
async waitForSelector<Selector extends keyof HTMLElementTagNameMap>(
|
||||
selector: Selector,
|
||||
options?: Exclude<WaitForSelectorOptions, 'root'>
|
||||
): Promise<ElementHandle<HTMLElementTagNameMap[Selector]> | null>;
|
||||
async waitForSelector(
|
||||
selector: string,
|
||||
options: {
|
||||
visible?: boolean;
|
||||
hidden?: boolean;
|
||||
timeout?: number;
|
||||
} = {}
|
||||
options?: Exclude<WaitForSelectorOptions, 'root'>
|
||||
): Promise<ElementHandle | null>;
|
||||
async waitForSelector(
|
||||
selector: string,
|
||||
options: Exclude<WaitForSelectorOptions, 'root'> = {}
|
||||
): Promise<ElementHandle | null> {
|
||||
const frame = this._context.frame();
|
||||
assert(frame);
|
||||
@ -539,10 +538,7 @@ export class ElementHandle<
|
||||
|
||||
async #scrollIntoViewIfNeeded(): Promise<void> {
|
||||
const error = await this.evaluate(
|
||||
async (
|
||||
element: Element,
|
||||
pageJavascriptEnabled: boolean
|
||||
): Promise<string | false> => {
|
||||
async (element, pageJavascriptEnabled): Promise<string | false> => {
|
||||
if (!element.isConnected) {
|
||||
return 'Node is detached from document';
|
||||
}
|
||||
@ -828,7 +824,7 @@ export class ElementHandle<
|
||||
);
|
||||
}
|
||||
|
||||
return this.evaluate((element: Element, vals: string[]): string[] => {
|
||||
return this.evaluate((element, vals): string[] => {
|
||||
const values = new Set(vals);
|
||||
if (!(element instanceof HTMLSelectElement)) {
|
||||
throw new Error('Element is not a <select> element.');
|
||||
@ -870,15 +866,13 @@ export class ElementHandle<
|
||||
* Note for locals script connecting to remote chrome environments,
|
||||
* paths must be absolute.
|
||||
*/
|
||||
async uploadFile(...filePaths: string[]): Promise<void> {
|
||||
const isMultiple = await this.evaluate<(element: Element) => boolean>(
|
||||
element => {
|
||||
if (!(element instanceof HTMLInputElement)) {
|
||||
throw new Error('uploadFile can only be called on an input element.');
|
||||
}
|
||||
return element.multiple;
|
||||
}
|
||||
);
|
||||
async uploadFile(
|
||||
this: ElementHandle<HTMLInputElement>,
|
||||
...filePaths: string[]
|
||||
): Promise<void> {
|
||||
const isMultiple = await this.evaluate(element => {
|
||||
return element.multiple;
|
||||
});
|
||||
assert(
|
||||
filePaths.length <= 1 || isMultiple,
|
||||
'Multiple file uploads only work with <input type=file multiple>'
|
||||
@ -912,7 +906,7 @@ export class ElementHandle<
|
||||
so the solution is to eval the element value to a new FileList directly.
|
||||
*/
|
||||
if (files.length === 0) {
|
||||
await (this as ElementHandle<HTMLInputElement>).evaluate(element => {
|
||||
await this.evaluate(element => {
|
||||
element.files = new DataTransfer().files;
|
||||
|
||||
// Dispatch events for this case because it should behave akin to a user action.
|
||||
@ -943,7 +937,10 @@ export class ElementHandle<
|
||||
* Calls {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus | focus} on the element.
|
||||
*/
|
||||
async focus(): Promise<void> {
|
||||
await (this as ElementHandle<HTMLElement>).evaluate(element => {
|
||||
await this.evaluate(element => {
|
||||
if (!(element instanceof HTMLElement)) {
|
||||
throw new Error('Cannot focus non-HTMLElement');
|
||||
}
|
||||
return element.focus();
|
||||
});
|
||||
}
|
||||
@ -1126,9 +1123,11 @@ export class ElementHandle<
|
||||
* @returns `null` if no element matches the selector.
|
||||
* @throws `Error` if the selector has no associated query handler.
|
||||
*/
|
||||
async $<T extends Element = Element>(
|
||||
selector: string
|
||||
): Promise<ElementHandle<T> | null> {
|
||||
async $<Selector extends keyof HTMLElementTagNameMap>(
|
||||
selector: Selector
|
||||
): Promise<ElementHandle<HTMLElementTagNameMap[Selector]> | null>;
|
||||
async $(selector: string): Promise<ElementHandle | null>;
|
||||
async $(selector: string): Promise<ElementHandle | null> {
|
||||
const {updatedSelector, queryHandler} =
|
||||
_getQueryHandlerAndSelector(selector);
|
||||
assert(
|
||||
@ -1149,9 +1148,11 @@ export class ElementHandle<
|
||||
* @returns `[]` if no element matches the selector.
|
||||
* @throws `Error` if the selector has no associated query handler.
|
||||
*/
|
||||
async $$<T extends Element = Element>(
|
||||
selector: string
|
||||
): Promise<Array<ElementHandle<T>>> {
|
||||
async $$<Selector extends keyof HTMLElementTagNameMap>(
|
||||
selector: Selector
|
||||
): Promise<ElementHandle<HTMLElementTagNameMap[Selector]>[]>;
|
||||
async $$(selector: string): Promise<ElementHandle[]>;
|
||||
async $$(selector: string): Promise<ElementHandle[]> {
|
||||
const {updatedSelector, queryHandler} =
|
||||
_getQueryHandlerAndSelector(selector);
|
||||
assert(
|
||||
@ -1176,37 +1177,46 @@ export class ElementHandle<
|
||||
* expect(await tweetHandle.$eval('.retweets', node => node.innerText)).toBe('10');
|
||||
* ```
|
||||
*/
|
||||
async $eval<ReturnType>(
|
||||
async $eval<
|
||||
Selector extends keyof HTMLElementTagNameMap,
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<
|
||||
[HTMLElementTagNameMap[Selector], ...Params]
|
||||
> = EvaluateFunc<[HTMLElementTagNameMap[Selector], ...Params]>
|
||||
>(
|
||||
selector: Selector,
|
||||
pageFunction: Func | string,
|
||||
...args: EvaluateParams<Params>
|
||||
): Promise<Awaited<ReturnType<Func>>>;
|
||||
async $eval<
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<[Element, ...Params]> = EvaluateFunc<
|
||||
[Element, ...Params]
|
||||
>
|
||||
>(
|
||||
selector: string,
|
||||
pageFunction: (
|
||||
element: Element,
|
||||
...args: unknown[]
|
||||
) => ReturnType | Promise<ReturnType>,
|
||||
...args: SerializableOrJSHandle[]
|
||||
): Promise<WrapElementHandle<ReturnType>> {
|
||||
pageFunction: Func | string,
|
||||
...args: EvaluateParams<Params>
|
||||
): Promise<Awaited<ReturnType<Func>>>;
|
||||
async $eval<
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<[Element, ...Params]> = EvaluateFunc<
|
||||
[Element, ...Params]
|
||||
>
|
||||
>(
|
||||
selector: string,
|
||||
pageFunction: Func | string,
|
||||
...args: EvaluateParams<Params>
|
||||
): Promise<Awaited<ReturnType<Func>>> {
|
||||
const elementHandle = await this.$(selector);
|
||||
if (!elementHandle) {
|
||||
throw new Error(
|
||||
`Error: failed to find element matching selector "${selector}"`
|
||||
);
|
||||
}
|
||||
const result = await elementHandle.evaluate<
|
||||
(
|
||||
element: Element,
|
||||
...args: SerializableOrJSHandle[]
|
||||
) => ReturnType | Promise<ReturnType>
|
||||
>(pageFunction, ...args);
|
||||
const result = await elementHandle.evaluate(pageFunction, ...args);
|
||||
await elementHandle.dispose();
|
||||
|
||||
/**
|
||||
* This `as` is a little unfortunate but helps TS understand the behavior of
|
||||
* `elementHandle.evaluate`. If evaluate 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>;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1232,28 +1242,44 @@ export class ElementHandle<
|
||||
* .toEqual(['Hello!', 'Hi!']);
|
||||
* ```
|
||||
*/
|
||||
async $$eval<ReturnType>(
|
||||
async $$eval<
|
||||
Selector extends keyof HTMLElementTagNameMap,
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<
|
||||
[HTMLElementTagNameMap[Selector][], ...Params]
|
||||
> = EvaluateFunc<[HTMLElementTagNameMap[Selector][], ...Params]>
|
||||
>(
|
||||
selector: Selector,
|
||||
pageFunction: Func | string,
|
||||
...args: EvaluateParams<Params>
|
||||
): Promise<Awaited<ReturnType<Func>>>;
|
||||
async $$eval<
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<[Element[], ...Params]> = EvaluateFunc<
|
||||
[Element[], ...Params]
|
||||
>
|
||||
>(
|
||||
selector: string,
|
||||
pageFunction: EvaluateFn<
|
||||
Element[],
|
||||
unknown,
|
||||
ReturnType | Promise<ReturnType>
|
||||
>,
|
||||
...args: SerializableOrJSHandle[]
|
||||
): Promise<WrapElementHandle<ReturnType>> {
|
||||
pageFunction: Func | string,
|
||||
...args: EvaluateParams<Params>
|
||||
): Promise<Awaited<ReturnType<Func>>>;
|
||||
async $$eval<
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<[Element[], ...Params]> = EvaluateFunc<
|
||||
[Element[], ...Params]
|
||||
>
|
||||
>(
|
||||
selector: string,
|
||||
pageFunction: Func | string,
|
||||
...args: EvaluateParams<Params>
|
||||
): Promise<Awaited<ReturnType<Func>>> {
|
||||
const {updatedSelector, queryHandler} =
|
||||
_getQueryHandlerAndSelector(selector);
|
||||
assert(queryHandler.queryAllArray);
|
||||
const arrayHandle = await queryHandler.queryAllArray(this, updatedSelector);
|
||||
const result = await arrayHandle.evaluate<EvaluateFn<Element[]>>(
|
||||
pageFunction,
|
||||
...args
|
||||
);
|
||||
const result = await arrayHandle.evaluate(pageFunction, ...args);
|
||||
await arrayHandle.dispose();
|
||||
/* This `as` exists for the same reason as the `as` in $eval above.
|
||||
* See the comment there for a full explanation.
|
||||
*/
|
||||
return result as WrapElementHandle<ReturnType>;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1262,24 +1288,21 @@ export class ElementHandle<
|
||||
* @param expression - Expression to {@link https://developer.mozilla.org/en-US/docs/Web/API/Document/evaluate | evaluate}
|
||||
*/
|
||||
async $x(expression: string): Promise<ElementHandle[]> {
|
||||
const arrayHandle = await this.evaluateHandle(
|
||||
(element: Document, expression: string) => {
|
||||
const document = element.ownerDocument || element;
|
||||
const iterator = document.evaluate(
|
||||
expression,
|
||||
element,
|
||||
null,
|
||||
XPathResult.ORDERED_NODE_ITERATOR_TYPE
|
||||
);
|
||||
const array = [];
|
||||
let item;
|
||||
while ((item = iterator.iterateNext())) {
|
||||
array.push(item);
|
||||
}
|
||||
return array;
|
||||
},
|
||||
expression
|
||||
);
|
||||
const arrayHandle = await this.evaluateHandle((element, expression) => {
|
||||
const document = element.ownerDocument || element;
|
||||
const iterator = document.evaluate(
|
||||
expression,
|
||||
element,
|
||||
null,
|
||||
XPathResult.ORDERED_NODE_ITERATOR_TYPE
|
||||
);
|
||||
const array = [];
|
||||
let item;
|
||||
while ((item = iterator.iterateNext())) {
|
||||
array.push(item);
|
||||
}
|
||||
return array;
|
||||
}, expression);
|
||||
const properties = await arrayHandle.getProperties();
|
||||
await arrayHandle.dispose();
|
||||
const result = [];
|
||||
@ -1298,8 +1321,8 @@ export class ElementHandle<
|
||||
async isIntersectingViewport(options?: {
|
||||
threshold?: number;
|
||||
}): Promise<boolean> {
|
||||
const {threshold = 0} = options || {};
|
||||
return await this.evaluate(async (element: Element, threshold: number) => {
|
||||
const {threshold = 0} = options ?? {};
|
||||
return await this.evaluate(async (element, threshold) => {
|
||||
const visibleRatio = await new Promise<number>(resolve => {
|
||||
const observer = new IntersectionObserver(entries => {
|
||||
resolve(entries[0]!.intersectionRatio);
|
||||
|
@ -23,15 +23,8 @@ import {CDPSession, CDPSessionEmittedEvents, Connection} from './Connection.js';
|
||||
import {ConsoleMessage, ConsoleMessageType} from './ConsoleMessage.js';
|
||||
import {Coverage} from './Coverage.js';
|
||||
import {Dialog} from './Dialog.js';
|
||||
import {WaitForSelectorOptions} from './DOMWorld.js';
|
||||
import {EmulationManager} from './EmulationManager.js';
|
||||
import {
|
||||
EvaluateFn,
|
||||
EvaluateFnReturnType,
|
||||
EvaluateHandleFn,
|
||||
SerializableOrJSHandle,
|
||||
UnwrapPromiseLike,
|
||||
WrapElementHandle,
|
||||
} from './EvalTypes.js';
|
||||
import {EventEmitter, Handler} from './EventEmitter.js';
|
||||
import {FileChooser} from './FileChooser.js';
|
||||
import {
|
||||
@ -39,6 +32,23 @@ import {
|
||||
FrameManager,
|
||||
FrameManagerEmittedEvents,
|
||||
} from './FrameManager.js';
|
||||
import {HTTPRequest} from './HTTPRequest.js';
|
||||
import {HTTPResponse} from './HTTPResponse.js';
|
||||
import {Keyboard, Mouse, MouseButton, Touchscreen} from './Input.js';
|
||||
import {ElementHandle, JSHandle, _createJSHandle} from './JSHandle.js';
|
||||
import {PuppeteerLifeCycleEvent} from './LifecycleWatcher.js';
|
||||
import {
|
||||
Credentials,
|
||||
NetworkConditions,
|
||||
NetworkManagerEmittedEvents,
|
||||
} from './NetworkManager.js';
|
||||
import {LowerCasePaperFormat, PDFOptions, _paperFormats} from './PDFOptions.js';
|
||||
import {Viewport} from './PuppeteerViewport.js';
|
||||
import {Target} from './Target.js';
|
||||
import {TaskQueue} from './TaskQueue.js';
|
||||
import {TimeoutSettings} from './TimeoutSettings.js';
|
||||
import {Tracing} from './Tracing.js';
|
||||
import {EvaluateFunc, EvaluateParams, HandleFor} from './types.js';
|
||||
import {
|
||||
debugError,
|
||||
evaluationString,
|
||||
@ -57,22 +67,6 @@ import {
|
||||
waitForEvent,
|
||||
waitWithTimeout,
|
||||
} from './util.js';
|
||||
import {HTTPRequest} from './HTTPRequest.js';
|
||||
import {HTTPResponse} from './HTTPResponse.js';
|
||||
import {Keyboard, Mouse, MouseButton, Touchscreen} from './Input.js';
|
||||
import {ElementHandle, JSHandle, _createJSHandle} from './JSHandle.js';
|
||||
import {PuppeteerLifeCycleEvent} from './LifecycleWatcher.js';
|
||||
import {
|
||||
Credentials,
|
||||
NetworkConditions,
|
||||
NetworkManagerEmittedEvents,
|
||||
} from './NetworkManager.js';
|
||||
import {LowerCasePaperFormat, PDFOptions, _paperFormats} from './PDFOptions.js';
|
||||
import {Viewport} from './PuppeteerViewport.js';
|
||||
import {Target} from './Target.js';
|
||||
import {TaskQueue} from './TaskQueue.js';
|
||||
import {TimeoutSettings} from './TimeoutSettings.js';
|
||||
import {Tracing} from './Tracing.js';
|
||||
import {WebWorker} from './WebWorker.js';
|
||||
|
||||
/**
|
||||
@ -466,9 +460,7 @@ export class Page extends EventEmitter {
|
||||
#viewport: Viewport | null;
|
||||
#screenshotTaskQueue: TaskQueue;
|
||||
#workers = new Map<string, WebWorker>();
|
||||
// TODO: improve this typedef - it's a function that takes a file chooser or
|
||||
// something?
|
||||
#fileChooserInterceptors = new Set<Function>();
|
||||
#fileChooserInterceptors = new Set<(chooser: FileChooser) => void>();
|
||||
|
||||
#disconnectPromise?: Promise<Error>;
|
||||
#userDragInterceptionEnabled = false;
|
||||
@ -638,9 +630,13 @@ export class Page extends EventEmitter {
|
||||
const element = await context._adoptBackendNodeId(event.backendNodeId);
|
||||
const interceptors = Array.from(this.#fileChooserInterceptors);
|
||||
this.#fileChooserInterceptors.clear();
|
||||
const fileChooser = new FileChooser(element, event);
|
||||
const fileChooser = new FileChooser(
|
||||
// This is guaranteed by the event.
|
||||
element as ElementHandle<HTMLInputElement>,
|
||||
event
|
||||
);
|
||||
for (const interceptor of interceptors) {
|
||||
interceptor.call(null, fileChooser);
|
||||
interceptor.call(undefined, fileChooser);
|
||||
}
|
||||
}
|
||||
|
||||
@ -736,7 +732,7 @@ export class Page extends EventEmitter {
|
||||
}
|
||||
|
||||
const {timeout = this.#timeoutSettings.timeout()} = options;
|
||||
let callback!: (value: FileChooser | PromiseLike<FileChooser>) => void;
|
||||
let callback!: (value: FileChooser) => void;
|
||||
const promise = new Promise<FileChooser>(x => {
|
||||
return (callback = x);
|
||||
});
|
||||
@ -1008,10 +1004,27 @@ export class Page extends EventEmitter {
|
||||
* {@link https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors | selector}
|
||||
* to query page for.
|
||||
*/
|
||||
async $<T extends Element = Element>(
|
||||
selector: string
|
||||
): Promise<ElementHandle<T> | null> {
|
||||
return this.mainFrame().$<T>(selector);
|
||||
async $<Selector extends keyof HTMLElementTagNameMap>(
|
||||
selector: Selector
|
||||
): Promise<ElementHandle<HTMLElementTagNameMap[Selector]> | null>;
|
||||
async $(selector: string): Promise<ElementHandle | null>;
|
||||
async $(selector: string): Promise<ElementHandle | null> {
|
||||
return this.mainFrame().$(selector);
|
||||
}
|
||||
|
||||
/**
|
||||
* The method runs `document.querySelectorAll` within the page. If no elements
|
||||
* match the selector, the return value resolves to `[]`.
|
||||
* @remarks
|
||||
* Shortcut for {@link Frame.$$ | Page.mainFrame().$$(selector) }.
|
||||
* @param selector - A `selector` to query page for
|
||||
*/
|
||||
async $$<Selector extends keyof HTMLElementTagNameMap>(
|
||||
selector: Selector
|
||||
): Promise<ElementHandle<HTMLElementTagNameMap[Selector]>[]>;
|
||||
async $$(selector: string): Promise<ElementHandle[]>;
|
||||
async $$(selector: string): Promise<ElementHandle[]> {
|
||||
return this.mainFrame().$$(selector);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1063,12 +1076,15 @@ export class Page extends EventEmitter {
|
||||
* @param pageFunction - a function that is run within the page
|
||||
* @param args - arguments to be passed to the pageFunction
|
||||
*/
|
||||
async evaluateHandle<HandlerType extends JSHandle = JSHandle>(
|
||||
pageFunction: EvaluateHandleFn,
|
||||
...args: SerializableOrJSHandle[]
|
||||
): Promise<HandlerType> {
|
||||
async evaluateHandle<
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
|
||||
>(
|
||||
pageFunction: Func | string,
|
||||
...args: EvaluateParams<Params>
|
||||
): Promise<HandleFor<Awaited<ReturnType<Func>>>> {
|
||||
const context = await this.mainFrame().executionContext();
|
||||
return context.evaluateHandle<HandlerType>(pageFunction, ...args);
|
||||
return context.evaluateHandle(pageFunction, ...args);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1098,7 +1114,9 @@ export class Page extends EventEmitter {
|
||||
* @returns Promise which resolves to a handle to an array of objects with
|
||||
* this prototype.
|
||||
*/
|
||||
async queryObjects(prototypeHandle: JSHandle): Promise<JSHandle> {
|
||||
async queryObjects<Prototype>(
|
||||
prototypeHandle: JSHandle<Prototype>
|
||||
): Promise<JSHandle<Prototype[]>> {
|
||||
const context = await this.mainFrame().executionContext();
|
||||
return context.queryObjects(prototypeHandle);
|
||||
}
|
||||
@ -1161,25 +1179,38 @@ export class Page extends EventEmitter {
|
||||
* is wrapped in an {@link ElementHandle}, else the raw value itself is
|
||||
* returned.
|
||||
*/
|
||||
async $eval<ReturnType>(
|
||||
async $eval<
|
||||
Selector extends keyof HTMLElementTagNameMap,
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<
|
||||
[HTMLElementTagNameMap[Selector], ...Params]
|
||||
> = EvaluateFunc<[HTMLElementTagNameMap[Selector], ...Params]>
|
||||
>(
|
||||
selector: Selector,
|
||||
pageFunction: Func | string,
|
||||
...args: EvaluateParams<Params>
|
||||
): Promise<Awaited<ReturnType<Func>>>;
|
||||
async $eval<
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<[Element, ...Params]> = EvaluateFunc<
|
||||
[Element, ...Params]
|
||||
>
|
||||
>(
|
||||
selector: 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/HEAD/types/puppeteer/index.d.ts#L114
|
||||
*/
|
||||
...args: unknown[]
|
||||
) => ReturnType | Promise<ReturnType>,
|
||||
...args: SerializableOrJSHandle[]
|
||||
): Promise<WrapElementHandle<ReturnType>> {
|
||||
return this.mainFrame().$eval<ReturnType>(selector, pageFunction, ...args);
|
||||
pageFunction: Func | string,
|
||||
...args: EvaluateParams<Params>
|
||||
): Promise<Awaited<ReturnType<Func>>>;
|
||||
async $eval<
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<[Element, ...Params]> = EvaluateFunc<
|
||||
[Element, ...Params]
|
||||
>
|
||||
>(
|
||||
selector: string,
|
||||
pageFunction: Func | string,
|
||||
...args: EvaluateParams<Params>
|
||||
): Promise<Awaited<ReturnType<Func>>> {
|
||||
return this.mainFrame().$eval(selector, pageFunction, ...args);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1244,32 +1275,38 @@ export class Page extends EventEmitter {
|
||||
* is wrapped in an {@link ElementHandle}, else the raw value itself is
|
||||
* returned.
|
||||
*/
|
||||
async $$eval<ReturnType>(
|
||||
async $$eval<
|
||||
Selector extends keyof HTMLElementTagNameMap,
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<
|
||||
[HTMLElementTagNameMap[Selector][], ...Params]
|
||||
> = EvaluateFunc<[HTMLElementTagNameMap[Selector][], ...Params]>
|
||||
>(
|
||||
selector: Selector,
|
||||
pageFunction: Func | string,
|
||||
...args: EvaluateParams<Params>
|
||||
): Promise<Awaited<ReturnType<Func>>>;
|
||||
async $$eval<
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<[Element[], ...Params]> = EvaluateFunc<
|
||||
[Element[], ...Params]
|
||||
>
|
||||
>(
|
||||
selector: string,
|
||||
pageFunction: (
|
||||
elements: Element[],
|
||||
/* These have to be typed as unknown[] for the same reason as the $eval
|
||||
* definition above, please see that comment for more details and the TODO
|
||||
* that will improve things.
|
||||
*/
|
||||
...args: unknown[]
|
||||
) => ReturnType | Promise<ReturnType>,
|
||||
...args: SerializableOrJSHandle[]
|
||||
): Promise<WrapElementHandle<ReturnType>> {
|
||||
return this.mainFrame().$$eval<ReturnType>(selector, pageFunction, ...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* The method runs `document.querySelectorAll` within the page. If no elements
|
||||
* match the selector, the return value resolves to `[]`.
|
||||
* @remarks
|
||||
* Shortcut for {@link Frame.$$ | Page.mainFrame().$$(selector) }.
|
||||
* @param selector - A `selector` to query page for
|
||||
*/
|
||||
async $$<T extends Element = Element>(
|
||||
selector: string
|
||||
): Promise<Array<ElementHandle<T>>> {
|
||||
return this.mainFrame().$$<T>(selector);
|
||||
pageFunction: Func | string,
|
||||
...args: EvaluateParams<Params>
|
||||
): Promise<Awaited<ReturnType<Func>>>;
|
||||
async $$eval<
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<[Element[], ...Params]> = EvaluateFunc<
|
||||
[Element[], ...Params]
|
||||
>
|
||||
>(
|
||||
selector: string,
|
||||
pageFunction: Func | string,
|
||||
...args: EvaluateParams<Params>
|
||||
): Promise<Awaited<ReturnType<Func>>> {
|
||||
return this.mainFrame().$$eval(selector, pageFunction, ...args);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1390,7 +1427,7 @@ export class Page extends EventEmitter {
|
||||
*
|
||||
* NOTE: Functions installed via `page.exposeFunction` survive navigations.
|
||||
* @param name - Name of the function on the window object
|
||||
* @param puppeteerFunction - Callback function which will be called in
|
||||
* @param pptrFunction - Callback function which will be called in
|
||||
* Puppeteer's context.
|
||||
* @example
|
||||
* An example of adding an `md5` function into the page:
|
||||
@ -1442,7 +1479,7 @@ export class Page extends EventEmitter {
|
||||
*/
|
||||
async exposeFunction(
|
||||
name: string,
|
||||
puppeteerFunction: Function | {default: Function}
|
||||
pptrFunction: Function | {default: Function}
|
||||
): Promise<void> {
|
||||
if (this.#pageBindings.has(name)) {
|
||||
throw new Error(
|
||||
@ -1451,14 +1488,13 @@ export class Page extends EventEmitter {
|
||||
}
|
||||
|
||||
let exposedFunction: Function;
|
||||
if (typeof puppeteerFunction === 'function') {
|
||||
exposedFunction = puppeteerFunction;
|
||||
} else if (typeof puppeteerFunction.default === 'function') {
|
||||
exposedFunction = puppeteerFunction.default;
|
||||
} else {
|
||||
throw new Error(
|
||||
`Failed to add page binding with name ${name}: ${puppeteerFunction} is not a function or a module with a default export.`
|
||||
);
|
||||
switch (typeof pptrFunction) {
|
||||
case 'function':
|
||||
exposedFunction = pptrFunction;
|
||||
break;
|
||||
default:
|
||||
exposedFunction = pptrFunction.default;
|
||||
break;
|
||||
}
|
||||
|
||||
this.#pageBindings.set(name, exposedFunction);
|
||||
@ -2640,11 +2676,14 @@ export class Page extends EventEmitter {
|
||||
*
|
||||
* @returns the return value of `pageFunction`.
|
||||
*/
|
||||
async evaluate<T extends EvaluateFn>(
|
||||
pageFunction: T,
|
||||
...args: SerializableOrJSHandle[]
|
||||
): Promise<UnwrapPromiseLike<EvaluateFnReturnType<T>>> {
|
||||
return this.#frameManager.mainFrame().evaluate<T>(pageFunction, ...args);
|
||||
async evaluate<
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
|
||||
>(
|
||||
pageFunction: Func | string,
|
||||
...args: EvaluateParams<Params>
|
||||
): Promise<Awaited<ReturnType<Func>>> {
|
||||
return this.#frameManager.mainFrame().evaluate(pageFunction, ...args);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2678,10 +2717,10 @@ export class Page extends EventEmitter {
|
||||
* await page.evaluateOnNewDocument(preloadFile);
|
||||
* ```
|
||||
*/
|
||||
async evaluateOnNewDocument(
|
||||
pageFunction: Function | string,
|
||||
...args: unknown[]
|
||||
): Promise<void> {
|
||||
async evaluateOnNewDocument<
|
||||
Params extends unknown[],
|
||||
Func extends (...args: Params) => unknown = (...args: Params) => unknown
|
||||
>(pageFunction: Func | string, ...args: Params): Promise<void> {
|
||||
const source = evaluationString(pageFunction, ...args);
|
||||
await this.#client.send('Page.addScriptToEvaluateOnNewDocument', {
|
||||
source,
|
||||
@ -3203,48 +3242,6 @@ export class Page extends EventEmitter {
|
||||
return this.mainFrame().type(selector, text, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @remarks
|
||||
*
|
||||
* This method behaves differently depending on the first parameter. If it's a
|
||||
* `string`, it will be treated as a `selector` or `xpath` (if the string
|
||||
* starts with `//`). This method then is a shortcut for
|
||||
* {@link Page.waitForSelector} or {@link Page.waitForXPath}.
|
||||
*
|
||||
* If the first argument is a function this method is a shortcut for
|
||||
* {@link Page.waitForFunction}.
|
||||
*
|
||||
* If the first argument is a `number`, it's treated as a timeout in
|
||||
* milliseconds and the method returns a promise which resolves after the
|
||||
* timeout.
|
||||
*
|
||||
* @param selectorOrFunctionOrTimeout - a selector, predicate or timeout to
|
||||
* wait for.
|
||||
* @param options - optional waiting parameters.
|
||||
* @param args - arguments to pass to `pageFunction`.
|
||||
*
|
||||
* @deprecated Don't use this method directly. Instead use the more explicit
|
||||
* methods available: {@link Page.waitForSelector},
|
||||
* {@link Page.waitForXPath}, {@link Page.waitForFunction} or
|
||||
* {@link Page.waitForTimeout}.
|
||||
*/
|
||||
waitFor(
|
||||
selectorOrFunctionOrTimeout: string | number | Function,
|
||||
options: {
|
||||
visible?: boolean;
|
||||
hidden?: boolean;
|
||||
timeout?: number;
|
||||
polling?: string | number;
|
||||
} = {},
|
||||
...args: SerializableOrJSHandle[]
|
||||
): Promise<JSHandle | null> {
|
||||
return this.mainFrame().waitFor(
|
||||
selectorOrFunctionOrTimeout,
|
||||
options,
|
||||
...args
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Causes your script to wait for the given number of milliseconds.
|
||||
*
|
||||
@ -3316,15 +3313,19 @@ export class Page extends EventEmitter {
|
||||
* (30 seconds). Pass `0` to disable timeout. The default value can be changed
|
||||
* by using the {@link Page.setDefaultTimeout} method.
|
||||
*/
|
||||
waitForSelector(
|
||||
async waitForSelector<Selector extends keyof HTMLElementTagNameMap>(
|
||||
selector: Selector,
|
||||
options?: Exclude<WaitForSelectorOptions, 'root'>
|
||||
): Promise<ElementHandle<HTMLElementTagNameMap[Selector]> | null>;
|
||||
async waitForSelector(
|
||||
selector: string,
|
||||
options: {
|
||||
visible?: boolean;
|
||||
hidden?: boolean;
|
||||
timeout?: number;
|
||||
} = {}
|
||||
options?: Exclude<WaitForSelectorOptions, 'root'>
|
||||
): Promise<ElementHandle | null>;
|
||||
async waitForSelector(
|
||||
selector: string,
|
||||
options: Exclude<WaitForSelectorOptions, 'root'> = {}
|
||||
): Promise<ElementHandle | null> {
|
||||
return this.mainFrame().waitForSelector(selector, options);
|
||||
return await this.mainFrame().waitForSelector(selector, options);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -3452,14 +3453,17 @@ export class Page extends EventEmitter {
|
||||
* {@link Page.setDefaultTimeout | page.setDefaultTimeout(timeout)} method.
|
||||
*
|
||||
*/
|
||||
waitForFunction(
|
||||
pageFunction: Function | string,
|
||||
waitForFunction<
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
|
||||
>(
|
||||
pageFunction: Func | string,
|
||||
options: {
|
||||
timeout?: number;
|
||||
polling?: string | number;
|
||||
} = {},
|
||||
...args: SerializableOrJSHandle[]
|
||||
): Promise<JSHandle> {
|
||||
...args: EvaluateParams<Params>
|
||||
): Promise<HandleFor<Awaited<ReturnType<Func>>>> {
|
||||
return this.mainFrame().waitForFunction(pageFunction, options, ...args);
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@
|
||||
|
||||
import {WaitForSelectorOptions, DOMWorld} from './DOMWorld.js';
|
||||
import {ElementHandle, JSHandle} from './JSHandle.js';
|
||||
import {_ariaHandler} from './AriaQueryHandler.js';
|
||||
import {ariaHandler} from './AriaQueryHandler.js';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@ -99,12 +99,13 @@ function makeQueryHandler(handler: CustomQueryHandler): InternalQueryHandler {
|
||||
return result;
|
||||
};
|
||||
internalHandler.queryAllArray = async (element, selector) => {
|
||||
const resultHandle = await element.evaluateHandle(queryAll, selector);
|
||||
const arrayHandle = await resultHandle.evaluateHandle(
|
||||
(res: Element[] | NodeListOf<Element>) => {
|
||||
return Array.from(res);
|
||||
}
|
||||
);
|
||||
const resultHandle = (await element.evaluateHandle(
|
||||
queryAll,
|
||||
selector
|
||||
)) as JSHandle<Element[] | NodeListOf<Element>>;
|
||||
const arrayHandle = await resultHandle.evaluateHandle(res => {
|
||||
return Array.from(res);
|
||||
});
|
||||
return arrayHandle;
|
||||
};
|
||||
}
|
||||
@ -172,7 +173,7 @@ const pierceHandler = makeQueryHandler({
|
||||
});
|
||||
|
||||
const builtInHandlers = new Map([
|
||||
['aria', _ariaHandler],
|
||||
['aria', ariaHandler],
|
||||
['pierce', pierceHandler],
|
||||
]);
|
||||
const queryHandlers = new Map(builtInHandlers);
|
||||
|
@ -16,11 +16,11 @@
|
||||
import {Protocol} from 'devtools-protocol';
|
||||
import {CDPSession} from './Connection.js';
|
||||
import {ConsoleMessageType} from './ConsoleMessage.js';
|
||||
import {EvaluateHandleFn, SerializableOrJSHandle} from './EvalTypes.js';
|
||||
import {EvaluateFunc, EvaluateParams, HandleFor} from './types.js';
|
||||
import {EventEmitter} from './EventEmitter.js';
|
||||
import {ExecutionContext} from './ExecutionContext.js';
|
||||
import {debugError} from './util.js';
|
||||
import {JSHandle} from './JSHandle.js';
|
||||
import {debugError} from './util.js';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@ -136,11 +136,14 @@ export class WebWorker extends EventEmitter {
|
||||
* @param args - Arguments to pass to `pageFunction`.
|
||||
* @returns Promise which resolves to the return value of `pageFunction`.
|
||||
*/
|
||||
async evaluate<ReturnType>(
|
||||
pageFunction: Function | string,
|
||||
...args: any[]
|
||||
): Promise<ReturnType> {
|
||||
return (await this.#executionContextPromise).evaluate<ReturnType>(
|
||||
async evaluate<
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
|
||||
>(
|
||||
pageFunction: Func | string,
|
||||
...args: EvaluateParams<Params>
|
||||
): Promise<Awaited<ReturnType<Func>>> {
|
||||
return (await this.#executionContextPromise).evaluate(
|
||||
pageFunction,
|
||||
...args
|
||||
);
|
||||
@ -158,11 +161,14 @@ export class WebWorker extends EventEmitter {
|
||||
* @param args - Arguments to pass to `pageFunction`.
|
||||
* @returns Promise which resolves to the return value of `pageFunction`.
|
||||
*/
|
||||
async evaluateHandle<HandlerType extends JSHandle = JSHandle>(
|
||||
pageFunction: EvaluateHandleFn,
|
||||
...args: SerializableOrJSHandle[]
|
||||
): Promise<JSHandle> {
|
||||
return (await this.#executionContextPromise).evaluateHandle<HandlerType>(
|
||||
async evaluateHandle<
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
|
||||
>(
|
||||
pageFunction: Func | string,
|
||||
...args: EvaluateParams<Params>
|
||||
): Promise<HandleFor<Awaited<ReturnType<Func>>>> {
|
||||
return (await this.#executionContextPromise).evaluateHandle(
|
||||
pageFunction,
|
||||
...args
|
||||
);
|
||||
|
32
src/common/types.ts
Normal file
32
src/common/types.ts
Normal file
@ -0,0 +1,32 @@
|
||||
/**
|
||||
* 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, ElementHandle} from './JSHandle.js';
|
||||
|
||||
export type Awaitable<T> = T | PromiseLike<T>;
|
||||
|
||||
export type HandleFor<T> = T extends Element ? ElementHandle<T> : JSHandle<T>;
|
||||
export type HandleOr<T> = HandleFor<T> | JSHandle<T> | T;
|
||||
|
||||
export type EvaluateParams<T extends unknown[]> = {
|
||||
[K in keyof T]: T[K] extends HandleOr<unknown> ? T[K] : HandleOr<T[K]>;
|
||||
};
|
||||
export type InnerParams<T extends unknown[]> = {
|
||||
[K in keyof T]: T[K] extends HandleOr<infer U> ? U : never;
|
||||
};
|
||||
export type EvaluateFunc<T extends unknown[]> = (
|
||||
...params: InnerParams<T>
|
||||
) => Awaitable<unknown>;
|
@ -276,8 +276,9 @@ describeChromeOnly('AriaQueryHandler', () => {
|
||||
page.waitForSelector('aria/anything'),
|
||||
page.setContent(`<h1>anything</h1>`),
|
||||
]);
|
||||
assert(handle);
|
||||
expect(
|
||||
await page.evaluate((x: HTMLElement) => {
|
||||
await page.evaluate(x => {
|
||||
return x.textContent;
|
||||
}, handle)
|
||||
).toBe('anything');
|
||||
@ -651,7 +652,9 @@ describeChromeOnly('AriaQueryHandler', () => {
|
||||
});
|
||||
it('should find by role "button"', async () => {
|
||||
const {page} = getTestState();
|
||||
const found = await page.$$<HTMLButtonElement>('aria/[role="button"]');
|
||||
const found = (await page.$$(
|
||||
'aria/[role="button"]'
|
||||
)) as ElementHandle<HTMLButtonElement>[];
|
||||
const ids = await getIds(found);
|
||||
expect(ids).toEqual([
|
||||
'node5',
|
||||
|
@ -17,10 +17,10 @@
|
||||
import expect from 'expect';
|
||||
import {
|
||||
getTestState,
|
||||
setupTestBrowserHooks,
|
||||
itFailsFirefox,
|
||||
setupTestBrowserHooks,
|
||||
} from './mocha-utils.js';
|
||||
import utils from './utils.js';
|
||||
import {waitEvent} from './utils.js';
|
||||
|
||||
describe('BrowserContext', function () {
|
||||
setupTestBrowserHooks();
|
||||
@ -67,8 +67,8 @@ describe('BrowserContext', function () {
|
||||
const page = await context.newPage();
|
||||
await page.goto(server.EMPTY_PAGE);
|
||||
const [popupTarget] = await Promise.all([
|
||||
utils.waitEvent(browser, 'targetcreated'),
|
||||
page.evaluate<(url: string) => void>(url => {
|
||||
waitEvent(browser, 'targetcreated'),
|
||||
page.evaluate(url => {
|
||||
return window.open(url);
|
||||
}, server.EMPTY_PAGE),
|
||||
]);
|
||||
|
@ -430,12 +430,12 @@ describe('Cookie specs', () => {
|
||||
|
||||
await page.goto(server.PREFIX + '/grid.html');
|
||||
await page.setCookie({name: 'localhost-cookie', value: 'best'});
|
||||
await page.evaluate<(src: string) => Promise<void>>(src => {
|
||||
await page.evaluate(src => {
|
||||
let fulfill!: () => void;
|
||||
const promise = new Promise<void>(x => {
|
||||
return (fulfill = x);
|
||||
});
|
||||
const iframe = document.createElement('iframe') as HTMLIFrameElement;
|
||||
const iframe = document.createElement('iframe');
|
||||
document.body.appendChild(iframe);
|
||||
iframe.onload = fulfill;
|
||||
iframe.src = src;
|
||||
@ -499,7 +499,7 @@ describe('Cookie specs', () => {
|
||||
|
||||
try {
|
||||
await page.goto(httpsServer.PREFIX + '/grid.html');
|
||||
await page.evaluate<(src: string) => Promise<void>>(src => {
|
||||
await page.evaluate(src => {
|
||||
let fulfill!: () => void;
|
||||
const promise = new Promise<void>(x => {
|
||||
return (fulfill = x);
|
||||
|
@ -287,7 +287,7 @@ describe('Coverage specs', function () {
|
||||
const {page, server} = getTestState();
|
||||
|
||||
await page.coverage.startCSSCoverage();
|
||||
await page.evaluate<(url: string) => Promise<void>>(async url => {
|
||||
await page.evaluate(async url => {
|
||||
document.body.textContent = 'hello, world';
|
||||
|
||||
const link = document.createElement('link');
|
||||
|
@ -17,15 +17,14 @@
|
||||
import expect from 'expect';
|
||||
import sinon from 'sinon';
|
||||
import {
|
||||
describeFailsFirefox,
|
||||
getTestState,
|
||||
itFailsFirefox,
|
||||
setupTestBrowserHooks,
|
||||
setupTestPageAndContextHooks,
|
||||
describeFailsFirefox,
|
||||
itFailsFirefox,
|
||||
} from './mocha-utils.js';
|
||||
|
||||
import utils from './utils.js';
|
||||
import {ElementHandle} from '../../lib/cjs/puppeteer/common/JSHandle.js';
|
||||
|
||||
describe('ElementHandle specs', function () {
|
||||
setupTestBrowserHooks();
|
||||
@ -86,7 +85,7 @@ describe('ElementHandle specs', function () {
|
||||
`);
|
||||
const element = (await page.$('#therect'))!;
|
||||
const pptrBoundingBox = await element.boundingBox();
|
||||
const webBoundingBox = await page.evaluate((e: HTMLElement) => {
|
||||
const webBoundingBox = await page.evaluate(e => {
|
||||
const rect = e.getBoundingClientRect();
|
||||
return {x: rect.x, y: rect.y, width: rect.width, height: rect.height};
|
||||
}, element);
|
||||
@ -189,9 +188,9 @@ describe('ElementHandle specs', function () {
|
||||
const {page, server} = getTestState();
|
||||
|
||||
await page.goto(server.PREFIX + '/shadow.html');
|
||||
const buttonHandle = await page.evaluateHandle<ElementHandle>(() => {
|
||||
const buttonHandle = await page.evaluateHandle(() => {
|
||||
// @ts-expect-error button is expected to be in the page's scope.
|
||||
return button;
|
||||
return button as HTMLButtonElement;
|
||||
});
|
||||
await buttonHandle.click();
|
||||
expect(
|
||||
@ -205,8 +204,8 @@ describe('ElementHandle specs', function () {
|
||||
const {page, server} = getTestState();
|
||||
|
||||
await page.goto(server.PREFIX + '/input/button.html');
|
||||
const buttonTextNode = await page.evaluateHandle<ElementHandle>(() => {
|
||||
return document.querySelector('button')!.firstChild;
|
||||
const buttonTextNode = await page.evaluateHandle(() => {
|
||||
return document.querySelector('button')!.firstChild as HTMLElement;
|
||||
});
|
||||
let error!: Error;
|
||||
await buttonTextNode.click().catch(error_ => {
|
||||
@ -401,7 +400,7 @@ describe('ElementHandle specs', function () {
|
||||
});
|
||||
const element = (await page.$('getById/foo'))!;
|
||||
expect(
|
||||
await page.evaluate<(element: HTMLElement) => string>(element => {
|
||||
await page.evaluate(element => {
|
||||
return element.id;
|
||||
}, element)
|
||||
).toBe('foo');
|
||||
@ -454,12 +453,9 @@ describe('ElementHandle specs', function () {
|
||||
const elements = await page.$$('getByClass/foo');
|
||||
const classNames = await Promise.all(
|
||||
elements.map(async element => {
|
||||
return await page.evaluate<(element: HTMLElement) => string>(
|
||||
element => {
|
||||
return element.className;
|
||||
},
|
||||
element
|
||||
);
|
||||
return await page.evaluate(element => {
|
||||
return element.className;
|
||||
}, element);
|
||||
})
|
||||
);
|
||||
|
||||
@ -539,7 +535,7 @@ describe('ElementHandle specs', function () {
|
||||
return element.querySelector(`.${selector}`);
|
||||
},
|
||||
});
|
||||
const waitFor = page.waitFor('getByClass/foo');
|
||||
const waitFor = page.waitForSelector('getByClass/foo');
|
||||
|
||||
// Set the page content after the waitFor has been started.
|
||||
await page.setContent(
|
||||
|
@ -357,7 +357,7 @@ describe('Evaluation specs', function () {
|
||||
return error_.message;
|
||||
});
|
||||
const error = await page
|
||||
.evaluate<(errorText: string) => Error>(errorText => {
|
||||
.evaluate(errorText => {
|
||||
throw new Error(errorText);
|
||||
}, errorText)
|
||||
.catch(error_ => {
|
||||
@ -477,7 +477,7 @@ describe('Evaluation specs', function () {
|
||||
it('should transfer 100Mb of data from page to node.js', async function () {
|
||||
const {page} = getTestState();
|
||||
|
||||
const a = await page.evaluate<() => string>(() => {
|
||||
const a = await page.evaluate(() => {
|
||||
return Array(100 * 1024 * 1024 + 1).join('a');
|
||||
});
|
||||
expect(a.length).toBe(100 * 1024 * 1024);
|
||||
|
@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
import expect from 'expect';
|
||||
import {ElementHandle} from '../../lib/cjs/puppeteer/common/JSHandle.js';
|
||||
import {
|
||||
getTestState,
|
||||
setupTestBrowserHooks,
|
||||
@ -29,8 +30,8 @@ describeFailsFirefox('Emulate idle state', () => {
|
||||
async function getIdleState() {
|
||||
const {page} = getTestState();
|
||||
|
||||
const stateElement = (await page.$('#state'))!;
|
||||
return await page.evaluate((element: HTMLElement) => {
|
||||
const stateElement = (await page.$('#state')) as ElementHandle<HTMLElement>;
|
||||
return await page.evaluate(element => {
|
||||
return element.innerText;
|
||||
}, stateElement);
|
||||
}
|
||||
|
@ -15,12 +15,11 @@
|
||||
*/
|
||||
|
||||
import expect from 'expect';
|
||||
import {JSHandle} from '../../lib/cjs/puppeteer/common/JSHandle.js';
|
||||
import {
|
||||
getTestState,
|
||||
itFailsFirefox,
|
||||
setupTestBrowserHooks,
|
||||
setupTestPageAndContextHooks,
|
||||
itFailsFirefox,
|
||||
shortWaitForArrayToHaveAtLeastNElements,
|
||||
} from './mocha-utils.js';
|
||||
|
||||
@ -43,7 +42,7 @@ describe('JSHandle', function () {
|
||||
const navigatorHandle = await page.evaluateHandle(() => {
|
||||
return navigator;
|
||||
});
|
||||
const text = await page.evaluate((e: Navigator) => {
|
||||
const text = await page.evaluate(e => {
|
||||
return e.userAgent;
|
||||
}, navigatorHandle);
|
||||
expect(text).toContain('Mozilla');
|
||||
@ -68,9 +67,10 @@ describe('JSHandle', function () {
|
||||
await page
|
||||
.evaluateHandle(
|
||||
opts => {
|
||||
// @ts-expect-error we are deliberately passing a bad type here
|
||||
// (nested object)
|
||||
return opts.elem;
|
||||
},
|
||||
// @ts-expect-error we are deliberately passing a bad type here (nested object)
|
||||
{test}
|
||||
)
|
||||
.catch(error_ => {
|
||||
@ -98,8 +98,8 @@ describe('JSHandle', function () {
|
||||
return window;
|
||||
});
|
||||
expect(
|
||||
await page.evaluate((e: {FOO: number}) => {
|
||||
return e.FOO;
|
||||
await page.evaluate(e => {
|
||||
return (e as any).FOO;
|
||||
}, aHandle)
|
||||
).toBe(123);
|
||||
});
|
||||
@ -119,21 +119,6 @@ describe('JSHandle', function () {
|
||||
const twoHandle = await aHandle.getProperty('two');
|
||||
expect(await twoHandle.jsonValue()).toEqual(2);
|
||||
});
|
||||
|
||||
it('should return a JSHandle even if the property does not exist', async () => {
|
||||
const {page} = getTestState();
|
||||
|
||||
const aHandle = await page.evaluateHandle(() => {
|
||||
return {
|
||||
one: 1,
|
||||
two: 2,
|
||||
three: 3,
|
||||
};
|
||||
});
|
||||
const undefinedHandle = await aHandle.getProperty('doesnotexist');
|
||||
expect(undefinedHandle).toBeInstanceOf(JSHandle);
|
||||
expect(await undefinedHandle.jsonValue()).toBe(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('JSHandle.jsonValue', function () {
|
||||
|
@ -467,7 +467,7 @@ describe('Keyboard', function () {
|
||||
await page.type('textarea', '👹 Tokyo street Japan 🇯🇵');
|
||||
expect(
|
||||
await page.$eval('textarea', textarea => {
|
||||
return (textarea as HTMLInputElement).value;
|
||||
return textarea.value;
|
||||
})
|
||||
).toBe('👹 Tokyo street Japan 🇯🇵');
|
||||
});
|
||||
@ -485,7 +485,7 @@ describe('Keyboard', function () {
|
||||
await textarea.type('👹 Tokyo street Japan 🇯🇵');
|
||||
expect(
|
||||
await frame.$eval('textarea', textarea => {
|
||||
return (textarea as HTMLInputElement).value;
|
||||
return textarea.value;
|
||||
})
|
||||
).toBe('👹 Tokyo street Japan 🇯🇵');
|
||||
});
|
||||
|
@ -61,7 +61,7 @@ describe('Mouse', function () {
|
||||
});
|
||||
});
|
||||
await page.mouse.click(50, 60);
|
||||
const event = await page.evaluate<() => MouseEvent>(() => {
|
||||
const event = await page.evaluate(() => {
|
||||
return (globalThis as any).clickPromise;
|
||||
});
|
||||
expect(event.type).toBe('click');
|
||||
@ -75,15 +75,13 @@ describe('Mouse', function () {
|
||||
const {page, server} = getTestState();
|
||||
|
||||
await page.goto(server.PREFIX + '/input/textarea.html');
|
||||
const {x, y, width, height} = await page.evaluate<() => Dimensions>(
|
||||
dimensions
|
||||
);
|
||||
const {x, y, width, height} = await page.evaluate(dimensions);
|
||||
const mouse = page.mouse;
|
||||
await mouse.move(x + width - 4, y + height - 4);
|
||||
await mouse.down();
|
||||
await mouse.move(x + width + 100, y + height + 100);
|
||||
await mouse.up();
|
||||
const newDimensions = await page.evaluate<() => Dimensions>(dimensions);
|
||||
const newDimensions = await page.evaluate(dimensions);
|
||||
expect(newDimensions.width).toBe(Math.round(width + 104));
|
||||
expect(newDimensions.height).toBe(Math.round(height + 104));
|
||||
});
|
||||
|
@ -422,7 +422,7 @@ describe('network', function () {
|
||||
});
|
||||
|
||||
// Trigger a request with a preflight.
|
||||
await page.evaluate<(src: string) => void>(async src => {
|
||||
await page.evaluate(async src => {
|
||||
const response = await fetch(src, {
|
||||
method: 'POST',
|
||||
headers: {'x-ping': 'pong'},
|
||||
@ -855,7 +855,7 @@ describe('network', function () {
|
||||
const response = await new Promise<HTTPResponse>(resolve => {
|
||||
page.on('response', resolve);
|
||||
const url = httpsServer.CROSS_PROCESS_PREFIX + '/setcookie.html';
|
||||
page.evaluate<(src: string) => void>(src => {
|
||||
page.evaluate(src => {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', src);
|
||||
xhr.send();
|
||||
|
@ -20,7 +20,6 @@ import path from 'path';
|
||||
import sinon from 'sinon';
|
||||
import {CDPSession} from '../../lib/cjs/puppeteer/common/Connection.js';
|
||||
import {ConsoleMessage} from '../../lib/cjs/puppeteer/common/ConsoleMessage.js';
|
||||
import {JSHandle} from '../../lib/cjs/puppeteer/common/JSHandle.js';
|
||||
import {Metrics, Page} from '../../lib/cjs/puppeteer/common/Page.js';
|
||||
import {
|
||||
describeFailsFirefox,
|
||||
@ -341,8 +340,8 @@ describe('Page', function () {
|
||||
});
|
||||
|
||||
describe('BrowserContext.overridePermissions', function () {
|
||||
function getPermission(page: Page, name: string) {
|
||||
return page.evaluate((name: PermissionName) => {
|
||||
function getPermission(page: Page, name: PermissionName) {
|
||||
return page.evaluate(name => {
|
||||
return navigator.permissions.query({name}).then(result => {
|
||||
return result.state;
|
||||
});
|
||||
@ -559,7 +558,7 @@ describe('Page', function () {
|
||||
return Set.prototype;
|
||||
});
|
||||
const objectsHandle = await page.queryObjects(prototypeHandle);
|
||||
const count = await page.evaluate((objects: JSHandle[]) => {
|
||||
const count = await page.evaluate(objects => {
|
||||
return objects.length;
|
||||
}, objectsHandle);
|
||||
expect(count).toBe(1);
|
||||
@ -580,7 +579,7 @@ describe('Page', function () {
|
||||
return Set.prototype;
|
||||
});
|
||||
const objectsHandle = await page.queryObjects(prototypeHandle);
|
||||
const count = await page.evaluate((objects: JSHandle[]) => {
|
||||
const count = await page.evaluate(objects => {
|
||||
return objects.length;
|
||||
}, objectsHandle);
|
||||
expect(count).toBe(1);
|
||||
@ -1246,11 +1245,9 @@ describe('Page', function () {
|
||||
return {x: a.x + b.x};
|
||||
}
|
||||
);
|
||||
const result = await page.evaluate<() => Promise<{x: number}>>(
|
||||
async () => {
|
||||
return (globalThis as any).complexObject({x: 5}, {x: 2});
|
||||
}
|
||||
);
|
||||
const result = await page.evaluate(async () => {
|
||||
return (globalThis as any).complexObject({x: 5}, {x: 2});
|
||||
});
|
||||
expect(result.x).toBe(7);
|
||||
});
|
||||
it('should fallback to default export when passed a module object', async () => {
|
||||
|
@ -433,7 +433,7 @@ describe('querySelector', function () {
|
||||
const html = (await page.$('html'))!;
|
||||
const second = await html.$x(`./body/div[contains(@class, 'second')]`);
|
||||
const inner = await second[0]!.$x(`./div[contains(@class, 'inner')]`);
|
||||
const content = await page.evaluate((e: HTMLElement) => {
|
||||
const content = await page.evaluate(e => {
|
||||
return e.textContent;
|
||||
}, inner[0]!);
|
||||
expect(content).toBe('A');
|
||||
@ -480,7 +480,7 @@ describe('querySelector', function () {
|
||||
const elements = await html.$$('allArray/div');
|
||||
expect(elements.length).toBe(2);
|
||||
const promises = elements.map(element => {
|
||||
return page.evaluate((e: HTMLElement) => {
|
||||
return page.evaluate(e => {
|
||||
return e.textContent;
|
||||
}, element);
|
||||
});
|
||||
|
@ -15,7 +15,6 @@
|
||||
*/
|
||||
|
||||
import expect from 'expect';
|
||||
import sinon from 'sinon';
|
||||
import {isErrorLike} from '../../lib/cjs/puppeteer/common/util.js';
|
||||
import {
|
||||
getTestState,
|
||||
@ -29,122 +28,6 @@ describe('waittask specs', function () {
|
||||
setupTestBrowserHooks();
|
||||
setupTestPageAndContextHooks();
|
||||
|
||||
describe('Page.waitFor', function () {
|
||||
/* This method is deprecated but we don't want the warnings showing up in
|
||||
* tests. Until we remove this method we still want to ensure we don't break
|
||||
* it.
|
||||
*/
|
||||
beforeEach(() => {
|
||||
return sinon.stub(console, 'warn').callsFake(() => {});
|
||||
});
|
||||
|
||||
it('should wait for selector', async () => {
|
||||
const {page, server} = getTestState();
|
||||
|
||||
let found = false;
|
||||
const waitFor = page.waitForSelector('div').then(() => {
|
||||
return (found = true);
|
||||
});
|
||||
await page.goto(server.EMPTY_PAGE);
|
||||
expect(found).toBe(false);
|
||||
await page.goto(server.PREFIX + '/grid.html');
|
||||
await waitFor;
|
||||
expect(found).toBe(true);
|
||||
});
|
||||
|
||||
it('should wait for an xpath', async () => {
|
||||
const {page, server} = getTestState();
|
||||
|
||||
let found = false;
|
||||
const waitFor = page.waitFor('//div').then(() => {
|
||||
return (found = true);
|
||||
});
|
||||
await page.goto(server.EMPTY_PAGE);
|
||||
expect(found).toBe(false);
|
||||
await page.goto(server.PREFIX + '/grid.html');
|
||||
await waitFor;
|
||||
expect(found).toBe(true);
|
||||
});
|
||||
it('should allow you to select an element with parenthesis-starting xpath', async () => {
|
||||
const {page, server} = getTestState();
|
||||
let found = false;
|
||||
const waitFor = page.waitFor('(//img)[200]').then(() => {
|
||||
found = true;
|
||||
});
|
||||
await page.goto(server.EMPTY_PAGE);
|
||||
expect(found).toBe(false);
|
||||
await page.goto(server.PREFIX + '/grid.html');
|
||||
await waitFor;
|
||||
expect(found).toBe(true);
|
||||
});
|
||||
it('should not allow you to select an element with single slash xpath', async () => {
|
||||
const {page} = getTestState();
|
||||
|
||||
await page.setContent(`<div>some text</div>`);
|
||||
let error!: Error;
|
||||
await page.waitFor('/html/body/div').catch(error_ => {
|
||||
return (error = error_);
|
||||
});
|
||||
expect(error).toBeTruthy();
|
||||
});
|
||||
it('should timeout', async () => {
|
||||
const {page} = getTestState();
|
||||
|
||||
const startTime = Date.now();
|
||||
const timeout = 42;
|
||||
await page.waitFor(timeout);
|
||||
expect(Date.now() - startTime).not.toBeLessThan(timeout / 2);
|
||||
});
|
||||
it('should work with multiline body', async () => {
|
||||
const {page} = getTestState();
|
||||
|
||||
const result = await page.waitForFunction(`
|
||||
(() => true)()
|
||||
`);
|
||||
expect(await result.jsonValue()).toBe(true);
|
||||
});
|
||||
it('should wait for predicate', async () => {
|
||||
const {page} = getTestState();
|
||||
|
||||
await Promise.all([
|
||||
page.waitFor(() => {
|
||||
return window.innerWidth < 100;
|
||||
}),
|
||||
page.setViewport({width: 10, height: 10}),
|
||||
]);
|
||||
});
|
||||
it('should wait for predicate with arguments', async () => {
|
||||
const {page} = getTestState();
|
||||
|
||||
await page.waitFor(
|
||||
(arg1: number, arg2: number) => {
|
||||
return arg1 !== arg2;
|
||||
},
|
||||
{},
|
||||
1,
|
||||
2
|
||||
);
|
||||
});
|
||||
|
||||
it('should log a deprecation warning', async () => {
|
||||
const {page} = getTestState();
|
||||
|
||||
await page.waitFor(() => {
|
||||
return true;
|
||||
});
|
||||
|
||||
const consoleWarnStub = console.warn as sinon.SinonSpy;
|
||||
|
||||
expect(consoleWarnStub.calledOnce).toBe(true);
|
||||
expect(
|
||||
consoleWarnStub.firstCall.calledWith(
|
||||
'waitFor is deprecated and will be removed in a future release. See https://github.com/puppeteer/puppeteer/issues/6214 for details and how to migrate your code.'
|
||||
)
|
||||
).toBe(true);
|
||||
expect((console.warn as sinon.SinonSpy).calledOnce).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Frame.waitForFunction', function () {
|
||||
it('should accept a string', async () => {
|
||||
const {page} = getTestState();
|
||||
|
Loading…
Reference in New Issue
Block a user