mirror of
https://github.com/puppeteer/puppeteer
synced 2024-06-14 14:02:48 +00:00
chore: Implement JSHandle for BiDi (#9660)
This commit is contained in:
parent
56f99f7b10
commit
0c85c0611c
@ -35,7 +35,7 @@ const windowHandle = await page.evaluateHandle(() => window);
|
|||||||
## Methods
|
## Methods
|
||||||
|
|
||||||
| Method | Modifiers | Description |
|
| Method | Modifiers | Description |
|
||||||
| ---------------------------------------------------------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
|
| ---------------------------------------------------------------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| [asElement()](./puppeteer.jshandle.aselement.md) | | |
|
| [asElement()](./puppeteer.jshandle.aselement.md) | | |
|
||||||
| [dispose()](./puppeteer.jshandle.dispose.md) | | Releases the object referenced by the handle for garbage collection. |
|
| [dispose()](./puppeteer.jshandle.dispose.md) | | Releases the object referenced by the handle for garbage collection. |
|
||||||
| [evaluate(pageFunction, args)](./puppeteer.jshandle.evaluate.md) | | Evaluates the given function with the current handle as its first argument. |
|
| [evaluate(pageFunction, args)](./puppeteer.jshandle.evaluate.md) | | Evaluates the given function with the current handle as its first argument. |
|
||||||
@ -45,5 +45,5 @@ const windowHandle = await page.evaluateHandle(() => window);
|
|||||||
| [getProperty(propertyName)](./puppeteer.jshandle.getproperty_1.md) | | |
|
| [getProperty(propertyName)](./puppeteer.jshandle.getproperty_1.md) | | |
|
||||||
| [getProperty(propertyName)](./puppeteer.jshandle.getproperty_2.md) | | |
|
| [getProperty(propertyName)](./puppeteer.jshandle.getproperty_2.md) | | |
|
||||||
| [jsonValue()](./puppeteer.jshandle.jsonvalue.md) | | |
|
| [jsonValue()](./puppeteer.jshandle.jsonvalue.md) | | |
|
||||||
| [remoteObject()](./puppeteer.jshandle.remoteobject.md) | | Provides access to the \[Protocol.Runtime.RemoteObject\](https://chromedevtools.github.io/devtools-protocol/tot/Runtime/\#type-RemoteObject) |
|
| [remoteObject()](./puppeteer.jshandle.remoteobject.md) | | Provides access to the \[Protocol.Runtime.RemoteObject\](https://chromedevtools.github.io/devtools-protocol/tot/Runtime/\#type-RemoteObject) backing this handle. |
|
||||||
| [toString()](./puppeteer.jshandle.tostring.md) | | Returns a string representation of the JSHandle. |
|
| [toString()](./puppeteer.jshandle.tostring.md) | | Returns a string representation of the JSHandle. |
|
||||||
|
@ -4,7 +4,7 @@ sidebar_label: JSHandle.remoteObject
|
|||||||
|
|
||||||
# JSHandle.remoteObject() method
|
# JSHandle.remoteObject() method
|
||||||
|
|
||||||
Provides access to the \[Protocol.Runtime.RemoteObject\](https://chromedevtools.github.io/devtools-protocol/tot/Runtime/\#type-RemoteObject)
|
Provides access to the \[Protocol.Runtime.RemoteObject\](https://chromedevtools.github.io/devtools-protocol/tot/Runtime/\#type-RemoteObject) backing this handle.
|
||||||
|
|
||||||
#### Signature:
|
#### Signature:
|
||||||
|
|
||||||
|
@ -177,9 +177,17 @@ export class JSHandle<T = unknown> {
|
|||||||
throw new Error('Not implemented');
|
throw new Error('Not implemented');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
get id(): string | undefined {
|
||||||
|
throw new Error('Not implemented');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides access to the
|
* Provides access to the
|
||||||
* [Protocol.Runtime.RemoteObject](https://chromedevtools.github.io/devtools-protocol/tot/Runtime/#type-RemoteObject)
|
* [Protocol.Runtime.RemoteObject](https://chromedevtools.github.io/devtools-protocol/tot/Runtime/#type-RemoteObject)
|
||||||
|
* backing this handle.
|
||||||
*/
|
*/
|
||||||
remoteObject(): Protocol.Runtime.RemoteObject {
|
remoteObject(): Protocol.Runtime.RemoteObject {
|
||||||
throw new Error('Not implemented');
|
throw new Error('Not implemented');
|
||||||
|
@ -186,7 +186,7 @@ export class Accessibility {
|
|||||||
let backendNodeId: number | undefined;
|
let backendNodeId: number | undefined;
|
||||||
if (root) {
|
if (root) {
|
||||||
const {node} = await this.#client.send('DOM.describeNode', {
|
const {node} = await this.#client.send('DOM.describeNode', {
|
||||||
objectId: root.remoteObject().objectId,
|
objectId: root.id,
|
||||||
});
|
});
|
||||||
backendNodeId = node.backendNodeId;
|
backendNodeId = node.backendNodeId;
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,7 @@ const queryAXTree = async (
|
|||||||
role?: string
|
role?: string
|
||||||
): Promise<Protocol.Accessibility.AXNode[]> => {
|
): Promise<Protocol.Accessibility.AXNode[]> => {
|
||||||
const {nodes} = await client.send('Accessibility.queryAXTree', {
|
const {nodes} = await client.send('Accessibility.queryAXTree', {
|
||||||
objectId: element.remoteObject().objectId,
|
objectId: element.id,
|
||||||
accessibleName,
|
accessibleName,
|
||||||
role,
|
role,
|
||||||
});
|
});
|
||||||
|
@ -43,12 +43,8 @@ import {
|
|||||||
NodeFor,
|
NodeFor,
|
||||||
} from './types.js';
|
} from './types.js';
|
||||||
import {KeyInput} from './USKeyboardLayout.js';
|
import {KeyInput} from './USKeyboardLayout.js';
|
||||||
import {
|
import {debugError, isString} from './util.js';
|
||||||
debugError,
|
import {CDPJSHandle} from './JSHandle.js';
|
||||||
isString,
|
|
||||||
releaseObject,
|
|
||||||
valueFromRemoteObject,
|
|
||||||
} from './util.js';
|
|
||||||
|
|
||||||
const applyOffsetsToQuad = (
|
const applyOffsetsToQuad = (
|
||||||
quad: Point[],
|
quad: Point[],
|
||||||
@ -70,10 +66,8 @@ const applyOffsetsToQuad = (
|
|||||||
export class CDPElementHandle<
|
export class CDPElementHandle<
|
||||||
ElementType extends Node = Element
|
ElementType extends Node = Element
|
||||||
> extends ElementHandle<ElementType> {
|
> extends ElementHandle<ElementType> {
|
||||||
#disposed = false;
|
|
||||||
#frame: Frame;
|
#frame: Frame;
|
||||||
#context: ExecutionContext;
|
#jsHandle: CDPJSHandle<ElementType>;
|
||||||
#remoteObject: Protocol.Runtime.RemoteObject;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
context: ExecutionContext,
|
context: ExecutionContext,
|
||||||
@ -81,8 +75,7 @@ export class CDPElementHandle<
|
|||||||
frame: Frame
|
frame: Frame
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.#context = context;
|
this.#jsHandle = new CDPJSHandle(context, remoteObject);
|
||||||
this.#remoteObject = remoteObject;
|
|
||||||
this.#frame = frame;
|
this.#frame = frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,18 +83,22 @@ export class CDPElementHandle<
|
|||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
override executionContext(): ExecutionContext {
|
override executionContext(): ExecutionContext {
|
||||||
return this.#context;
|
return this.#jsHandle.executionContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
override get client(): CDPSession {
|
override get client(): CDPSession {
|
||||||
return this.#context._client;
|
return this.#jsHandle.client;
|
||||||
|
}
|
||||||
|
|
||||||
|
override get id(): string | undefined {
|
||||||
|
return this.#jsHandle.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
override remoteObject(): Protocol.Runtime.RemoteObject {
|
override remoteObject(): Protocol.Runtime.RemoteObject {
|
||||||
return this.#remoteObject;
|
return this.#jsHandle.remoteObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
override async evaluate<
|
override async evaluate<
|
||||||
@ -143,7 +140,7 @@ export class CDPElementHandle<
|
|||||||
}
|
}
|
||||||
|
|
||||||
override get disposed(): boolean {
|
override get disposed(): boolean {
|
||||||
return this.#disposed;
|
return this.#jsHandle.disposed;
|
||||||
}
|
}
|
||||||
|
|
||||||
override async getProperty<K extends keyof ElementType>(
|
override async getProperty<K extends keyof ElementType>(
|
||||||
@ -153,30 +150,27 @@ export class CDPElementHandle<
|
|||||||
override async getProperty<K extends keyof ElementType>(
|
override async getProperty<K extends keyof ElementType>(
|
||||||
propertyName: HandleOr<K>
|
propertyName: HandleOr<K>
|
||||||
): Promise<HandleFor<ElementType[K]>> {
|
): Promise<HandleFor<ElementType[K]>> {
|
||||||
return this.evaluateHandle((object, propertyName) => {
|
return this.#jsHandle.getProperty(propertyName);
|
||||||
return object[propertyName as K];
|
}
|
||||||
}, propertyName);
|
|
||||||
|
override async getProperties(): Promise<Map<string, JSHandle>> {
|
||||||
|
return this.#jsHandle.getProperties();
|
||||||
|
}
|
||||||
|
|
||||||
|
override asElement(): CDPElementHandle<ElementType> | null {
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
override async jsonValue(): Promise<ElementType> {
|
override async jsonValue(): Promise<ElementType> {
|
||||||
if (!this.#remoteObject.objectId) {
|
return this.#jsHandle.jsonValue();
|
||||||
return valueFromRemoteObject(this.#remoteObject);
|
|
||||||
}
|
|
||||||
const value = await this.evaluate(object => {
|
|
||||||
return object;
|
|
||||||
});
|
|
||||||
if (value === undefined) {
|
|
||||||
throw new Error('Could not serialize referenced object');
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override toString(): string {
|
override toString(): string {
|
||||||
if (!this.#remoteObject.objectId) {
|
return this.#jsHandle.toString();
|
||||||
return 'JSHandle:' + valueFromRemoteObject(this.#remoteObject);
|
|
||||||
}
|
}
|
||||||
const type = this.#remoteObject.subtype || this.#remoteObject.type;
|
|
||||||
return 'JSHandle@' + type;
|
override async dispose(): Promise<void> {
|
||||||
|
return await this.#jsHandle.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
override async $<Selector extends string>(
|
override async $<Selector extends string>(
|
||||||
@ -297,10 +291,6 @@ export class CDPElementHandle<
|
|||||||
return this as unknown as HandleFor<ElementFor<K>>;
|
return this as unknown as HandleFor<ElementFor<K>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
override asElement(): CDPElementHandle<ElementType> | null {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
override async contentFrame(): Promise<Frame | null> {
|
override async contentFrame(): Promise<Frame | null> {
|
||||||
const nodeInfo = await this.client.send('DOM.describeNode', {
|
const nodeInfo = await this.client.send('DOM.describeNode', {
|
||||||
objectId: this.remoteObject().objectId,
|
objectId: this.remoteObject().objectId,
|
||||||
@ -464,7 +454,7 @@ export class CDPElementHandle<
|
|||||||
|
|
||||||
#getBoxModel(): Promise<void | Protocol.DOM.GetBoxModelResponse> {
|
#getBoxModel(): Promise<void | Protocol.DOM.GetBoxModelResponse> {
|
||||||
const params: Protocol.DOM.GetBoxModelRequest = {
|
const params: Protocol.DOM.GetBoxModelRequest = {
|
||||||
objectId: this.remoteObject().objectId,
|
objectId: this.id,
|
||||||
};
|
};
|
||||||
return this.client.send('DOM.getBoxModel', params).catch(error => {
|
return this.client.send('DOM.getBoxModel', params).catch(error => {
|
||||||
return debugError(error);
|
return debugError(error);
|
||||||
@ -846,14 +836,6 @@ export class CDPElementHandle<
|
|||||||
return threshold === 1 ? visibleRatio === 1 : visibleRatio > threshold;
|
return threshold === 1 ? visibleRatio === 1 : visibleRatio > threshold;
|
||||||
}, threshold);
|
}, threshold);
|
||||||
}
|
}
|
||||||
|
|
||||||
override async dispose(): Promise<void> {
|
|
||||||
if (this.#disposed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.#disposed = true;
|
|
||||||
await releaseObject(this.client, this.#remoteObject);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function computeQuadArea(quad: Point[]): number {
|
function computeQuadArea(quad: Point[]): number {
|
||||||
|
@ -29,6 +29,8 @@ import {
|
|||||||
stringifyFunction,
|
stringifyFunction,
|
||||||
valueFromRemoteObject,
|
valueFromRemoteObject,
|
||||||
} from './util.js';
|
} from './util.js';
|
||||||
|
import {CDPJSHandle} from './JSHandle.js';
|
||||||
|
import {CDPElementHandle} from './ElementHandle.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
@ -321,7 +323,10 @@ export class ExecutionContext {
|
|||||||
if (Object.is(arg, NaN)) {
|
if (Object.is(arg, NaN)) {
|
||||||
return {unserializableValue: 'NaN'};
|
return {unserializableValue: 'NaN'};
|
||||||
}
|
}
|
||||||
const objectHandle = arg && arg instanceof JSHandle ? arg : null;
|
const objectHandle =
|
||||||
|
arg && (arg instanceof CDPJSHandle || arg instanceof CDPElementHandle)
|
||||||
|
? arg
|
||||||
|
: null;
|
||||||
if (objectHandle) {
|
if (objectHandle) {
|
||||||
if (objectHandle.executionContext() !== this) {
|
if (objectHandle.executionContext() !== this) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
@ -539,7 +539,7 @@ export class IsolatedWorld {
|
|||||||
'Cannot adopt handle that already belongs to this execution context'
|
'Cannot adopt handle that already belongs to this execution context'
|
||||||
);
|
);
|
||||||
const nodeInfo = await this.#client.send('DOM.describeNode', {
|
const nodeInfo = await this.#client.send('DOM.describeNode', {
|
||||||
objectId: handle.remoteObject().objectId,
|
objectId: handle.id,
|
||||||
});
|
});
|
||||||
return (await this.adoptBackendNode(nodeInfo.node.backendNodeId)) as T;
|
return (await this.adoptBackendNode(nodeInfo.node.backendNodeId)) as T;
|
||||||
}
|
}
|
||||||
|
@ -156,6 +156,10 @@ export class CDPJSHandle<T> extends JSHandle<T> {
|
|||||||
return 'JSHandle@' + type;
|
return 'JSHandle@' + type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override get id(): string | undefined {
|
||||||
|
return this.#remoteObject.objectId;
|
||||||
|
}
|
||||||
|
|
||||||
override remoteObject(): Protocol.Runtime.RemoteObject {
|
override remoteObject(): Protocol.Runtime.RemoteObject {
|
||||||
return this.#remoteObject;
|
return this.#remoteObject;
|
||||||
}
|
}
|
||||||
|
@ -525,13 +525,12 @@ export class CDPPage extends Page {
|
|||||||
): Promise<JSHandle<Prototype[]>> {
|
): Promise<JSHandle<Prototype[]>> {
|
||||||
const context = await this.mainFrame().executionContext();
|
const context = await this.mainFrame().executionContext();
|
||||||
assert(!prototypeHandle.disposed, 'Prototype JSHandle is disposed!');
|
assert(!prototypeHandle.disposed, 'Prototype JSHandle is disposed!');
|
||||||
const remoteObject = prototypeHandle.remoteObject();
|
|
||||||
assert(
|
assert(
|
||||||
remoteObject.objectId,
|
prototypeHandle.id,
|
||||||
'Prototype JSHandle must not be referencing primitive value'
|
'Prototype JSHandle must not be referencing primitive value'
|
||||||
);
|
);
|
||||||
const response = await context._client.send('Runtime.queryObjects', {
|
const response = await context._client.send('Runtime.queryObjects', {
|
||||||
prototypeObjectId: remoteObject.objectId,
|
prototypeObjectId: prototypeHandle.id,
|
||||||
});
|
});
|
||||||
return createJSHandle(context, response.objects) as HandleFor<Prototype[]>;
|
return createJSHandle(context, response.objects) as HandleFor<Prototype[]>;
|
||||||
}
|
}
|
||||||
@ -820,7 +819,7 @@ export class CDPPage extends Page {
|
|||||||
}
|
}
|
||||||
const textTokens = [];
|
const textTokens = [];
|
||||||
for (const arg of args) {
|
for (const arg of args) {
|
||||||
const remoteObject = arg.remoteObject();
|
const remoteObject = arg.remoteObject() as Protocol.Runtime.RemoteObject;
|
||||||
if (remoteObject.objectId) {
|
if (remoteObject.objectId) {
|
||||||
textTokens.push(arg.toString());
|
textTokens.push(arg.toString());
|
||||||
} else {
|
} else {
|
||||||
|
@ -36,6 +36,10 @@ interface Commands {
|
|||||||
params: Bidi.Script.CallFunctionParameters;
|
params: Bidi.Script.CallFunctionParameters;
|
||||||
returnType: Bidi.Script.CallFunctionResult;
|
returnType: Bidi.Script.CallFunctionResult;
|
||||||
};
|
};
|
||||||
|
'script.disown': {
|
||||||
|
params: Bidi.Script.DisownParameters;
|
||||||
|
returnType: Bidi.Script.DisownResult;
|
||||||
|
};
|
||||||
'browsingContext.create': {
|
'browsingContext.create': {
|
||||||
params: Bidi.BrowsingContext.CreateParameters;
|
params: Bidi.BrowsingContext.CreateParameters;
|
||||||
returnType: Bidi.BrowsingContext.CreateResult;
|
returnType: Bidi.BrowsingContext.CreateResult;
|
||||||
|
146
packages/puppeteer-core/src/common/bidi/JSHandle.ts
Normal file
146
packages/puppeteer-core/src/common/bidi/JSHandle.ts
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2023 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 {ElementHandle} from '../../api/ElementHandle.js';
|
||||||
|
import {EvaluateFuncWith, HandleFor, HandleOr} from '../../common/types.js';
|
||||||
|
import {releaseReference} from './utils.js';
|
||||||
|
import {Page} from './Page.js';
|
||||||
|
import {JSHandle as BaseJSHandle} from '../../api/JSHandle.js';
|
||||||
|
import {BidiSerializer} from './Serializer.js';
|
||||||
|
import {Connection} from './Connection.js';
|
||||||
|
import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
|
||||||
|
|
||||||
|
export class JSHandle<T = unknown> extends BaseJSHandle<T> {
|
||||||
|
#disposed = false;
|
||||||
|
#context;
|
||||||
|
#remoteValue;
|
||||||
|
|
||||||
|
constructor(context: Page, remoteValue: Bidi.CommonDataTypes.RemoteValue) {
|
||||||
|
super();
|
||||||
|
this.#context = context;
|
||||||
|
this.#remoteValue = remoteValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
context(): Page {
|
||||||
|
return this.#context;
|
||||||
|
}
|
||||||
|
|
||||||
|
get connecton(): Connection {
|
||||||
|
return this.#context.connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
override get disposed(): boolean {
|
||||||
|
return this.#disposed;
|
||||||
|
}
|
||||||
|
|
||||||
|
override async evaluate<
|
||||||
|
Params extends unknown[],
|
||||||
|
Func extends EvaluateFuncWith<T, Params> = EvaluateFuncWith<T, Params>
|
||||||
|
>(
|
||||||
|
pageFunction: Func | string,
|
||||||
|
...args: Params
|
||||||
|
): Promise<Awaited<ReturnType<Func>>> {
|
||||||
|
return await this.context().evaluate(pageFunction, this, ...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
override async evaluateHandle<
|
||||||
|
Params extends unknown[],
|
||||||
|
Func extends EvaluateFuncWith<T, Params> = EvaluateFuncWith<T, Params>
|
||||||
|
>(
|
||||||
|
pageFunction: Func | string,
|
||||||
|
...args: Params
|
||||||
|
): Promise<HandleFor<Awaited<ReturnType<Func>>>> {
|
||||||
|
return await this.context().evaluateHandle(pageFunction, this, ...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
override async getProperty<K extends keyof T>(
|
||||||
|
propertyName: HandleOr<K>
|
||||||
|
): Promise<HandleFor<T[K]>>;
|
||||||
|
override async getProperty(propertyName: string): Promise<HandleFor<unknown>>;
|
||||||
|
override async getProperty<K extends keyof T>(
|
||||||
|
propertyName: HandleOr<K>
|
||||||
|
): Promise<HandleFor<T[K]>> {
|
||||||
|
return await this.evaluateHandle((object, propertyName) => {
|
||||||
|
return object[propertyName as K];
|
||||||
|
}, propertyName);
|
||||||
|
}
|
||||||
|
|
||||||
|
override async getProperties(): Promise<Map<string, BaseJSHandle>> {
|
||||||
|
// TODO(lightning00blade): Either include return of depth Handles in RemoteValue
|
||||||
|
// or new BiDi command that returns array of remote value
|
||||||
|
const keys = await this.evaluate(object => {
|
||||||
|
return Object.getOwnPropertyNames(object);
|
||||||
|
});
|
||||||
|
const map: Map<string, BaseJSHandle> = new Map();
|
||||||
|
const results = await Promise.all(
|
||||||
|
keys.map(key => {
|
||||||
|
return this.getProperty(key);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(keys)) {
|
||||||
|
const handle = results[key as any];
|
||||||
|
if (handle) {
|
||||||
|
map.set(value, handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
override async jsonValue(): Promise<T> {
|
||||||
|
if (!('handle' in this.#remoteValue)) {
|
||||||
|
return BidiSerializer.deserialize(this.#remoteValue);
|
||||||
|
}
|
||||||
|
const value = await this.evaluate(object => {
|
||||||
|
return object;
|
||||||
|
});
|
||||||
|
if (value === undefined) {
|
||||||
|
throw new Error('Could not serialize referenced object');
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
override asElement(): ElementHandle<Node> | null {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
override async dispose(): Promise<void> {
|
||||||
|
if (this.#disposed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.#disposed = true;
|
||||||
|
if ('handle' in this.#remoteValue) {
|
||||||
|
await releaseReference(this.connecton, this.#remoteValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override toString(): string {
|
||||||
|
if (!('handle' in this.#remoteValue)) {
|
||||||
|
return 'JSHandle:' + BidiSerializer.deserialize(this.#remoteValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'JSHandle@' + this.#remoteValue.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
override get id(): string | undefined {
|
||||||
|
return 'handle' in this.#remoteValue ? this.#remoteValue.handle : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
bidiObject(): Bidi.CommonDataTypes.RemoteValue {
|
||||||
|
return this.#remoteValue;
|
||||||
|
}
|
||||||
|
}
|
@ -16,28 +16,45 @@
|
|||||||
|
|
||||||
import {Page as PageBase} from '../../api/Page.js';
|
import {Page as PageBase} from '../../api/Page.js';
|
||||||
import {Connection} from './Connection.js';
|
import {Connection} from './Connection.js';
|
||||||
import type {EvaluateFunc} from '../types.js';
|
import type {EvaluateFunc, HandleFor} from '../types.js';
|
||||||
import {isString, stringifyFunction} from '../util.js';
|
import {isString, stringifyFunction} from '../util.js';
|
||||||
import {BidiSerializer} from './Serializer.js';
|
import {BidiSerializer} from './Serializer.js';
|
||||||
|
import {JSHandle} from './JSHandle.js';
|
||||||
|
import {Reference} from './types.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
export class Page extends PageBase {
|
export class Page extends PageBase {
|
||||||
#connection: Connection;
|
#connection: Connection;
|
||||||
#contextId: string;
|
_contextId: string;
|
||||||
|
|
||||||
constructor(connection: Connection, contextId: string) {
|
constructor(connection: Connection, contextId: string) {
|
||||||
super();
|
super();
|
||||||
this.#connection = connection;
|
this.#connection = connection;
|
||||||
this.#contextId = contextId;
|
this._contextId = contextId;
|
||||||
}
|
}
|
||||||
|
|
||||||
override async close(): Promise<void> {
|
override async close(): Promise<void> {
|
||||||
await this.#connection.send('browsingContext.close', {
|
await this.#connection.send('browsingContext.close', {
|
||||||
context: this.#contextId,
|
context: this._contextId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get connection(): Connection {
|
||||||
|
return this.#connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
override async evaluateHandle<
|
||||||
|
Params extends unknown[],
|
||||||
|
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
|
||||||
|
>(
|
||||||
|
pageFunction: Func | string,
|
||||||
|
...args: Params
|
||||||
|
): Promise<HandleFor<Awaited<ReturnType<Func>>>> {
|
||||||
|
return this.#evaluate(false, pageFunction, ...args);
|
||||||
|
}
|
||||||
|
|
||||||
override async evaluate<
|
override async evaluate<
|
||||||
Params extends unknown[],
|
Params extends unknown[],
|
||||||
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
|
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
|
||||||
@ -45,18 +62,52 @@ export class Page extends PageBase {
|
|||||||
pageFunction: Func | string,
|
pageFunction: Func | string,
|
||||||
...args: Params
|
...args: Params
|
||||||
): Promise<Awaited<ReturnType<Func>>> {
|
): Promise<Awaited<ReturnType<Func>>> {
|
||||||
|
return this.#evaluate(true, pageFunction, ...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
async #evaluate<
|
||||||
|
Params extends unknown[],
|
||||||
|
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
|
||||||
|
>(
|
||||||
|
returnByValue: true,
|
||||||
|
pageFunction: Func | string,
|
||||||
|
...args: Params
|
||||||
|
): Promise<Awaited<ReturnType<Func>>>;
|
||||||
|
async #evaluate<
|
||||||
|
Params extends unknown[],
|
||||||
|
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
|
||||||
|
>(
|
||||||
|
returnByValue: false,
|
||||||
|
pageFunction: Func | string,
|
||||||
|
...args: Params
|
||||||
|
): Promise<HandleFor<Awaited<ReturnType<Func>>>>;
|
||||||
|
async #evaluate<
|
||||||
|
Params extends unknown[],
|
||||||
|
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
|
||||||
|
>(
|
||||||
|
returnByValue: boolean,
|
||||||
|
pageFunction: Func | string,
|
||||||
|
...args: Params
|
||||||
|
): Promise<HandleFor<Awaited<ReturnType<Func>>> | Awaited<ReturnType<Func>>> {
|
||||||
let responsePromise;
|
let responsePromise;
|
||||||
|
const resultOwnership = returnByValue ? 'none' : 'root';
|
||||||
if (isString(pageFunction)) {
|
if (isString(pageFunction)) {
|
||||||
responsePromise = this.#connection.send('script.evaluate', {
|
responsePromise = this.#connection.send('script.evaluate', {
|
||||||
expression: pageFunction,
|
expression: pageFunction,
|
||||||
target: {context: this.#contextId},
|
target: {context: this._contextId},
|
||||||
|
resultOwnership,
|
||||||
awaitPromise: true,
|
awaitPromise: true,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
responsePromise = this.#connection.send('script.callFunction', {
|
responsePromise = this.#connection.send('script.callFunction', {
|
||||||
functionDeclaration: stringifyFunction(pageFunction),
|
functionDeclaration: stringifyFunction(pageFunction),
|
||||||
arguments: await Promise.all(args.map(BidiSerializer.serialize)),
|
arguments: await Promise.all(
|
||||||
target: {context: this.#contextId},
|
args.map(arg => {
|
||||||
|
return BidiSerializer.serialize(arg, this);
|
||||||
|
})
|
||||||
|
),
|
||||||
|
target: {context: this._contextId},
|
||||||
|
resultOwnership,
|
||||||
awaitPromise: true,
|
awaitPromise: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -67,8 +118,22 @@ export class Page extends PageBase {
|
|||||||
throw new Error(result.exceptionDetails.text);
|
throw new Error(result.exceptionDetails.text);
|
||||||
}
|
}
|
||||||
|
|
||||||
return BidiSerializer.deserialize(result.result) as Awaited<
|
return returnByValue
|
||||||
ReturnType<Func>
|
? BidiSerializer.deserialize(result.result)
|
||||||
>;
|
: getBidiHandle(this, result.result as Reference);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
export function getBidiHandle(context: Page, result: Reference): JSHandle {
|
||||||
|
// TODO: | ElementHandle<Node>
|
||||||
|
if (
|
||||||
|
(result.type === 'node' || result.type === 'window') &&
|
||||||
|
context._contextId
|
||||||
|
) {
|
||||||
|
throw new Error('ElementHandle not implemented');
|
||||||
|
}
|
||||||
|
return new JSHandle(context, result);
|
||||||
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
|
import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
|
||||||
import {debugError, isDate, isPlainObject, isRegExp} from '../util.js';
|
import {debugError, isDate, isPlainObject, isRegExp} from '../util.js';
|
||||||
|
import {JSHandle} from './JSHandle.js';
|
||||||
|
import {Page} from './Page.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
@ -46,6 +48,18 @@ export class BidiSerializer {
|
|||||||
value: parsedArray,
|
value: parsedArray,
|
||||||
};
|
};
|
||||||
} else if (isPlainObject(arg)) {
|
} else if (isPlainObject(arg)) {
|
||||||
|
try {
|
||||||
|
JSON.stringify(arg);
|
||||||
|
} catch (error) {
|
||||||
|
if (
|
||||||
|
error instanceof TypeError &&
|
||||||
|
error.message.startsWith('Converting circular structure to JSON')
|
||||||
|
) {
|
||||||
|
error.message += ' Recursive objects are not allowed.';
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
const parsedObject: Bidi.CommonDataTypes.MappingLocalValue = [];
|
const parsedObject: Bidi.CommonDataTypes.MappingLocalValue = [];
|
||||||
for (const key in arg) {
|
for (const key in arg) {
|
||||||
parsedObject.push([
|
parsedObject.push([
|
||||||
@ -112,8 +126,24 @@ export class BidiSerializer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static serialize(arg: unknown): Bidi.CommonDataTypes.LocalOrRemoteValue {
|
static serialize(
|
||||||
|
arg: unknown,
|
||||||
|
context: Page
|
||||||
|
): Bidi.CommonDataTypes.LocalOrRemoteValue {
|
||||||
// TODO: See use case of LazyArgs
|
// TODO: See use case of LazyArgs
|
||||||
|
const objectHandle = arg && arg instanceof JSHandle ? arg : null;
|
||||||
|
if (objectHandle) {
|
||||||
|
if (objectHandle.context() !== context) {
|
||||||
|
throw new Error(
|
||||||
|
'JSHandles can be evaluated only in the context they were created!'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (objectHandle.disposed) {
|
||||||
|
throw new Error('JSHandle is disposed!');
|
||||||
|
}
|
||||||
|
return objectHandle.bidiObject();
|
||||||
|
}
|
||||||
|
|
||||||
return BidiSerializer.serializeRemoveValue(arg);
|
return BidiSerializer.serializeRemoveValue(arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,8 +176,8 @@ export class BidiSerializer {
|
|||||||
});
|
});
|
||||||
case 'set':
|
case 'set':
|
||||||
// TODO: Check expected output when value is undefined
|
// TODO: Check expected output when value is undefined
|
||||||
return result.value.reduce((acc: Set<unknown>, value: unknown) => {
|
return result.value.reduce((acc: Set<unknown>, value) => {
|
||||||
return acc.add(value);
|
return acc.add(BidiSerializer.deserializeLocalValue(value));
|
||||||
}, new Set());
|
}, new Set());
|
||||||
case 'object':
|
case 'object':
|
||||||
if (result.value) {
|
if (result.value) {
|
||||||
@ -202,7 +232,7 @@ export class BidiSerializer {
|
|||||||
return {key, value};
|
return {key, value};
|
||||||
}
|
}
|
||||||
|
|
||||||
static deserialize(result: Bidi.CommonDataTypes.RemoteValue): unknown {
|
static deserialize(result: Bidi.CommonDataTypes.RemoteValue): any {
|
||||||
if (!result) {
|
if (!result) {
|
||||||
debugError('Service did not produce a result.');
|
debugError('Service did not produce a result.');
|
||||||
return undefined;
|
return undefined;
|
||||||
|
6
packages/puppeteer-core/src/common/bidi/types.ts
Normal file
6
packages/puppeteer-core/src/common/bidi/types.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
|
||||||
|
|
||||||
|
export type Reference = Extract<
|
||||||
|
Bidi.CommonDataTypes.RemoteValue,
|
||||||
|
Bidi.CommonDataTypes.RemoteReference
|
||||||
|
>;
|
45
packages/puppeteer-core/src/common/bidi/utils.ts
Normal file
45
packages/puppeteer-core/src/common/bidi/utils.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2023 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 {debug} from '../Debug.js';
|
||||||
|
import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
|
||||||
|
import {Connection} from './Connection.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
export const debugError = debug('puppeteer:error');
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
export async function releaseReference(
|
||||||
|
client: Connection,
|
||||||
|
remoteReference: Bidi.CommonDataTypes.RemoteReference
|
||||||
|
): Promise<void> {
|
||||||
|
if (!remoteReference.handle) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await client
|
||||||
|
.send('script.disown', {
|
||||||
|
target: {realm: '', context: ''}, // TODO: Populate
|
||||||
|
handles: [remoteReference.handle],
|
||||||
|
})
|
||||||
|
.catch((error: any) => {
|
||||||
|
// Exceptions might happen in case of a page been navigated or closed.
|
||||||
|
// Swallow these since they are harmless and we don't leak anything in this case.
|
||||||
|
debugError(error);
|
||||||
|
});
|
||||||
|
}
|
@ -414,8 +414,8 @@
|
|||||||
"expectations": ["SKIP"]
|
"expectations": ["SKIP"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"testIdPattern": "[jshandle.spec] JSHandle JSHandle.click should work",
|
"testIdPattern": "[elementhandle.spec] ElementHandle specs ElementHandle.click should return Point data",
|
||||||
"platforms": ["darwin", "linux", "win32"],
|
"platforms": ["darwin", "win32", "linux"],
|
||||||
"parameters": ["firefox"],
|
"parameters": ["firefox"],
|
||||||
"expectations": ["FAIL"]
|
"expectations": ["FAIL"]
|
||||||
},
|
},
|
||||||
@ -1756,5 +1756,59 @@
|
|||||||
"platforms": ["darwin", "linux", "win32"],
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
"parameters": ["webDriverBiDi"],
|
"parameters": ["webDriverBiDi"],
|
||||||
"expectations": ["SKIP"]
|
"expectations": ["SKIP"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"testIdPattern": "[jshandle.spec]",
|
||||||
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
|
"parameters": ["webDriverBiDi"],
|
||||||
|
"expectations": ["PASS"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"testIdPattern": "[jshandle.spec] JSHandle Page.evaluateHandle should work",
|
||||||
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
|
"parameters": ["webDriverBiDi"],
|
||||||
|
"expectations": ["FAIL"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"testIdPattern": "[jshandle.spec] JSHandle Page.evaluateHandle should return the RemoteObject",
|
||||||
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
|
"parameters": ["webDriverBiDi"],
|
||||||
|
"expectations": ["FAIL"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"testIdPattern": "[jshandle.spec] JSHandle Page.evaluateHandle should use the same JS wrappers",
|
||||||
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
|
"parameters": ["webDriverBiDi"],
|
||||||
|
"expectations": ["FAIL"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"testIdPattern": "[jshandle.spec] JSHandle JSHandle.jsonValue should not work with dates",
|
||||||
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
|
"parameters": ["webDriverBiDi"],
|
||||||
|
"expectations": ["FAIL"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"testIdPattern": "[jshandle.spec] JSHandle JSHandle.asElement should work",
|
||||||
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
|
"parameters": ["webDriverBiDi"],
|
||||||
|
"expectations": ["FAIL"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"testIdPattern": "[jshandle.spec] JSHandle JSHandle.asElement should return ElementHandle for TextNodes",
|
||||||
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
|
"parameters": ["webDriverBiDi"],
|
||||||
|
"expectations": ["FAIL"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"testIdPattern": "[jshandle.spec] JSHandle JSHandle.toString should work for complicated objects",
|
||||||
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
|
"parameters": ["webDriverBiDi"],
|
||||||
|
"expectations": ["FAIL"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"testIdPattern": "[jshandle.spec] JSHandle JSHandle.toString should work with different subtypes",
|
||||||
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
|
"parameters": ["webDriverBiDi"],
|
||||||
|
"expectations": ["FAIL"]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -21,6 +21,7 @@ import {
|
|||||||
getTestState,
|
getTestState,
|
||||||
setupTestBrowserHooks,
|
setupTestBrowserHooks,
|
||||||
setupTestPageAndContextHooks,
|
setupTestPageAndContextHooks,
|
||||||
|
shortWaitForArrayToHaveAtLeastNElements,
|
||||||
} from './mocha-utils.js';
|
} from './mocha-utils.js';
|
||||||
|
|
||||||
import {Puppeteer} from 'puppeteer';
|
import {Puppeteer} from 'puppeteer';
|
||||||
@ -173,7 +174,6 @@ describe('ElementHandle specs', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('ElementHandle.click', function () {
|
describe('ElementHandle.click', function () {
|
||||||
// See https://github.com/puppeteer/puppeteer/issues/7175
|
|
||||||
it('should work', async () => {
|
it('should work', async () => {
|
||||||
const {page, server} = getTestState();
|
const {page, server} = getTestState();
|
||||||
|
|
||||||
@ -186,6 +186,40 @@ describe('ElementHandle specs', function () {
|
|||||||
})
|
})
|
||||||
).toBe('Clicked');
|
).toBe('Clicked');
|
||||||
});
|
});
|
||||||
|
it('should return Point data', async () => {
|
||||||
|
const {page} = getTestState();
|
||||||
|
|
||||||
|
const clicks: Array<[x: number, y: number]> = [];
|
||||||
|
|
||||||
|
await page.exposeFunction('reportClick', (x: number, y: number): void => {
|
||||||
|
clicks.push([x, y]);
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.evaluate(() => {
|
||||||
|
document.body.style.padding = '0';
|
||||||
|
document.body.style.margin = '0';
|
||||||
|
document.body.innerHTML = `
|
||||||
|
<div style="cursor: pointer; width: 120px; height: 60px; margin: 30px; padding: 15px;"></div>
|
||||||
|
`;
|
||||||
|
document.body.addEventListener('click', e => {
|
||||||
|
(window as any).reportClick(e.clientX, e.clientY);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const divHandle = (await page.$('div'))!;
|
||||||
|
await divHandle.click();
|
||||||
|
await divHandle.click({
|
||||||
|
offset: {
|
||||||
|
x: 10,
|
||||||
|
y: 15,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await shortWaitForArrayToHaveAtLeastNElements(clicks, 2);
|
||||||
|
expect(clicks).toEqual([
|
||||||
|
[45 + 60, 45 + 30], // margin + middle point offset
|
||||||
|
[30 + 10, 30 + 15], // margin + offset
|
||||||
|
]);
|
||||||
|
});
|
||||||
it('should work for Shadow DOM v1', async () => {
|
it('should work for Shadow DOM v1', async () => {
|
||||||
const {page, server} = getTestState();
|
const {page, server} = getTestState();
|
||||||
|
|
||||||
@ -273,6 +307,70 @@ describe('ElementHandle specs', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('ElementHandle.clickablePoint', function () {
|
||||||
|
it('should work', async () => {
|
||||||
|
const {page} = getTestState();
|
||||||
|
|
||||||
|
await page.evaluate(() => {
|
||||||
|
document.body.style.padding = '0';
|
||||||
|
document.body.style.margin = '0';
|
||||||
|
document.body.innerHTML = `
|
||||||
|
<div style="cursor: pointer; width: 120px; height: 60px; margin: 30px; padding: 15px;"></div>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
await page.evaluate(async () => {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
return window.requestAnimationFrame(resolve);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
const divHandle = (await page.$('div'))!;
|
||||||
|
expect(await divHandle.clickablePoint()).toEqual({
|
||||||
|
x: 45 + 60, // margin + middle point offset
|
||||||
|
y: 45 + 30, // margin + middle point offset
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
await divHandle.clickablePoint({
|
||||||
|
x: 10,
|
||||||
|
y: 15,
|
||||||
|
})
|
||||||
|
).toEqual({
|
||||||
|
x: 30 + 10, // margin + offset
|
||||||
|
y: 30 + 15, // margin + offset
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work for iframes', async () => {
|
||||||
|
const {page} = getTestState();
|
||||||
|
await page.evaluate(() => {
|
||||||
|
document.body.style.padding = '10px';
|
||||||
|
document.body.style.margin = '10px';
|
||||||
|
document.body.innerHTML = `
|
||||||
|
<iframe style="border: none; margin: 0; padding: 0;" seamless sandbox srcdoc="<style>* { margin: 0; padding: 0;}</style><div style='cursor: pointer; width: 120px; height: 60px; margin: 30px; padding: 15px;' />"></iframe>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
await page.evaluate(async () => {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
return window.requestAnimationFrame(resolve);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
const frame = page.frames()[1]!;
|
||||||
|
const divHandle = (await frame.$('div'))!;
|
||||||
|
expect(await divHandle.clickablePoint()).toEqual({
|
||||||
|
x: 20 + 45 + 60, // iframe pos + margin + middle point offset
|
||||||
|
y: 20 + 45 + 30, // iframe pos + margin + middle point offset
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
await divHandle.clickablePoint({
|
||||||
|
x: 10,
|
||||||
|
y: 15,
|
||||||
|
})
|
||||||
|
).toEqual({
|
||||||
|
x: 20 + 30 + 10, // iframe pos + margin + offset
|
||||||
|
y: 20 + 30 + 15, // iframe pos + margin + offset
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('Element.waitForSelector', () => {
|
describe('Element.waitForSelector', () => {
|
||||||
it('should wait correctly with waitForSelector on an element', async () => {
|
it('should wait correctly with waitForSelector on an element', async () => {
|
||||||
const {page} = getTestState();
|
const {page} = getTestState();
|
||||||
|
@ -19,7 +19,6 @@ import {
|
|||||||
getTestState,
|
getTestState,
|
||||||
setupTestBrowserHooks,
|
setupTestBrowserHooks,
|
||||||
setupTestPageAndContextHooks,
|
setupTestPageAndContextHooks,
|
||||||
shortWaitForArrayToHaveAtLeastNElements,
|
|
||||||
} from './mocha-utils.js';
|
} from './mocha-utils.js';
|
||||||
|
|
||||||
describe('JSHandle', function () {
|
describe('JSHandle', function () {
|
||||||
@ -336,105 +335,4 @@ describe('JSHandle', function () {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('JSHandle.clickablePoint', function () {
|
|
||||||
it('should work', async () => {
|
|
||||||
const {page} = getTestState();
|
|
||||||
|
|
||||||
await page.evaluate(() => {
|
|
||||||
document.body.style.padding = '0';
|
|
||||||
document.body.style.margin = '0';
|
|
||||||
document.body.innerHTML = `
|
|
||||||
<div style="cursor: pointer; width: 120px; height: 60px; margin: 30px; padding: 15px;"></div>
|
|
||||||
`;
|
|
||||||
});
|
|
||||||
await page.evaluate(async () => {
|
|
||||||
return new Promise(resolve => {
|
|
||||||
return window.requestAnimationFrame(resolve);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
const divHandle = (await page.$('div'))!;
|
|
||||||
expect(await divHandle.clickablePoint()).toEqual({
|
|
||||||
x: 45 + 60, // margin + middle point offset
|
|
||||||
y: 45 + 30, // margin + middle point offset
|
|
||||||
});
|
|
||||||
expect(
|
|
||||||
await divHandle.clickablePoint({
|
|
||||||
x: 10,
|
|
||||||
y: 15,
|
|
||||||
})
|
|
||||||
).toEqual({
|
|
||||||
x: 30 + 10, // margin + offset
|
|
||||||
y: 30 + 15, // margin + offset
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should work for iframes', async () => {
|
|
||||||
const {page} = getTestState();
|
|
||||||
await page.evaluate(() => {
|
|
||||||
document.body.style.padding = '10px';
|
|
||||||
document.body.style.margin = '10px';
|
|
||||||
document.body.innerHTML = `
|
|
||||||
<iframe style="border: none; margin: 0; padding: 0;" seamless sandbox srcdoc="<style>* { margin: 0; padding: 0;}</style><div style='cursor: pointer; width: 120px; height: 60px; margin: 30px; padding: 15px;' />"></iframe>
|
|
||||||
`;
|
|
||||||
});
|
|
||||||
await page.evaluate(async () => {
|
|
||||||
return new Promise(resolve => {
|
|
||||||
return window.requestAnimationFrame(resolve);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
const frame = page.frames()[1]!;
|
|
||||||
const divHandle = (await frame.$('div'))!;
|
|
||||||
expect(await divHandle.clickablePoint()).toEqual({
|
|
||||||
x: 20 + 45 + 60, // iframe pos + margin + middle point offset
|
|
||||||
y: 20 + 45 + 30, // iframe pos + margin + middle point offset
|
|
||||||
});
|
|
||||||
expect(
|
|
||||||
await divHandle.clickablePoint({
|
|
||||||
x: 10,
|
|
||||||
y: 15,
|
|
||||||
})
|
|
||||||
).toEqual({
|
|
||||||
x: 20 + 30 + 10, // iframe pos + margin + offset
|
|
||||||
y: 20 + 30 + 15, // iframe pos + margin + offset
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('JSHandle.click', function () {
|
|
||||||
it('should work', async () => {
|
|
||||||
const {page} = getTestState();
|
|
||||||
|
|
||||||
const clicks: Array<[x: number, y: number]> = [];
|
|
||||||
|
|
||||||
await page.exposeFunction('reportClick', (x: number, y: number): void => {
|
|
||||||
clicks.push([x, y]);
|
|
||||||
});
|
|
||||||
|
|
||||||
await page.evaluate(() => {
|
|
||||||
document.body.style.padding = '0';
|
|
||||||
document.body.style.margin = '0';
|
|
||||||
document.body.innerHTML = `
|
|
||||||
<div style="cursor: pointer; width: 120px; height: 60px; margin: 30px; padding: 15px;"></div>
|
|
||||||
`;
|
|
||||||
document.body.addEventListener('click', e => {
|
|
||||||
(window as any).reportClick(e.clientX, e.clientY);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const divHandle = (await page.$('div'))!;
|
|
||||||
await divHandle.click();
|
|
||||||
await divHandle.click({
|
|
||||||
offset: {
|
|
||||||
x: 10,
|
|
||||||
y: 15,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
await shortWaitForArrayToHaveAtLeastNElements(clicks, 2);
|
|
||||||
expect(clicks).toEqual([
|
|
||||||
[45 + 60, 45 + 30], // margin + middle point offset
|
|
||||||
[30 + 10, 30 + 15], // margin + offset
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user