2019-01-15 04:34:50 +00:00
|
|
|
/**
|
|
|
|
* Copyright 2019 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.
|
|
|
|
*/
|
|
|
|
|
2020-04-21 11:11:06 +00:00
|
|
|
import {helper, assert, debugError} from './helper';
|
chore: migrate src/ExecutionContext (#5705)
* chore: migrate src/ExecutionContext to TypeScript
I spent a while trying to decide on the best course of action for
typing the `evaluate` function.
Ideally I wanted to use generics so that as a user you could type
something like:
```
handle.evaluate<HTMLElement, number, boolean>((node, x) => true, 5)
```
And have TypeScript know the arguments of `node` and `x` based on those
generics. But I hit two problems with that:
* you have to have n overloads of `evaluate` to cope for as many number
of arguments as you can be bothered too (e.g. we'd need an overload for
1 arg, 2 args, 3 args, etc)
* I decided it's actually confusing because you don't know as a user
what those generics actually map to.
So in the end I went with one generic which is the return type of the
function:
```
handle.evaluate<boolean>((node, x) => true, 5)
```
And `node` and `x` get typed as `any` which means you can tell TS
yourself:
```
handle.evaluate<boolean>((node: HTMLElement, x: number) => true, 5)
```
I'd like to find a way to force that the arguments after the function do
match the arguments you've given (in the above example, TS would moan if
I swapped that `5` for `"foo"`), but I tried a few things and to be
honest the complexity of the types wasn't worth it, I don't think.
I'm very open to tweaking these but I'd rather ship this and tweak going
forwards rather than spend hours now tweaking. Once we ship these
typedefs and get feedback from the community I'm sure we can improve
them.
2020-04-22 09:33:44 +00:00
|
|
|
import {ExecutionContext} from './ExecutionContext';
|
2020-04-21 11:11:06 +00:00
|
|
|
import {CDPSession} from './Connection';
|
|
|
|
|
|
|
|
interface BoxModel {
|
|
|
|
content: Array<{x: number; y: number}>;
|
|
|
|
padding: Array<{x: number; y: number}>;
|
|
|
|
border: Array<{x: number; y: number}>;
|
|
|
|
margin: Array<{x: number; y: number}>;
|
|
|
|
width: number;
|
|
|
|
height: number;
|
|
|
|
}
|
2019-01-15 04:34:50 +00:00
|
|
|
|
chore: migrate src/ExecutionContext (#5705)
* chore: migrate src/ExecutionContext to TypeScript
I spent a while trying to decide on the best course of action for
typing the `evaluate` function.
Ideally I wanted to use generics so that as a user you could type
something like:
```
handle.evaluate<HTMLElement, number, boolean>((node, x) => true, 5)
```
And have TypeScript know the arguments of `node` and `x` based on those
generics. But I hit two problems with that:
* you have to have n overloads of `evaluate` to cope for as many number
of arguments as you can be bothered too (e.g. we'd need an overload for
1 arg, 2 args, 3 args, etc)
* I decided it's actually confusing because you don't know as a user
what those generics actually map to.
So in the end I went with one generic which is the return type of the
function:
```
handle.evaluate<boolean>((node, x) => true, 5)
```
And `node` and `x` get typed as `any` which means you can tell TS
yourself:
```
handle.evaluate<boolean>((node: HTMLElement, x: number) => true, 5)
```
I'd like to find a way to force that the arguments after the function do
match the arguments you've given (in the above example, TS would moan if
I swapped that `5` for `"foo"`), but I tried a few things and to be
honest the complexity of the types wasn't worth it, I don't think.
I'm very open to tweaking these but I'd rather ship this and tweak going
forwards rather than spend hours now tweaking. Once we ship these
typedefs and get feedback from the community I'm sure we can improve
them.
2020-04-22 09:33:44 +00:00
|
|
|
export function createJSHandle(context: ExecutionContext, remoteObject: Protocol.Runtime.RemoteObject): JSHandle {
|
2019-01-15 04:34:50 +00:00
|
|
|
const frame = context.frame();
|
|
|
|
if (remoteObject.subtype === 'node' && frame) {
|
|
|
|
const frameManager = frame._frameManager;
|
|
|
|
return new ElementHandle(context, context._client, remoteObject, frameManager.page(), frameManager);
|
|
|
|
}
|
|
|
|
return new JSHandle(context, context._client, remoteObject);
|
|
|
|
}
|
|
|
|
|
2020-04-21 11:11:06 +00:00
|
|
|
export class JSHandle {
|
chore: migrate src/ExecutionContext (#5705)
* chore: migrate src/ExecutionContext to TypeScript
I spent a while trying to decide on the best course of action for
typing the `evaluate` function.
Ideally I wanted to use generics so that as a user you could type
something like:
```
handle.evaluate<HTMLElement, number, boolean>((node, x) => true, 5)
```
And have TypeScript know the arguments of `node` and `x` based on those
generics. But I hit two problems with that:
* you have to have n overloads of `evaluate` to cope for as many number
of arguments as you can be bothered too (e.g. we'd need an overload for
1 arg, 2 args, 3 args, etc)
* I decided it's actually confusing because you don't know as a user
what those generics actually map to.
So in the end I went with one generic which is the return type of the
function:
```
handle.evaluate<boolean>((node, x) => true, 5)
```
And `node` and `x` get typed as `any` which means you can tell TS
yourself:
```
handle.evaluate<boolean>((node: HTMLElement, x: number) => true, 5)
```
I'd like to find a way to force that the arguments after the function do
match the arguments you've given (in the above example, TS would moan if
I swapped that `5` for `"foo"`), but I tried a few things and to be
honest the complexity of the types wasn't worth it, I don't think.
I'm very open to tweaking these but I'd rather ship this and tweak going
forwards rather than spend hours now tweaking. Once we ship these
typedefs and get feedback from the community I'm sure we can improve
them.
2020-04-22 09:33:44 +00:00
|
|
|
_context: ExecutionContext;
|
2020-04-21 11:11:06 +00:00
|
|
|
_client: CDPSession;
|
|
|
|
_remoteObject: Protocol.Runtime.RemoteObject;
|
|
|
|
_disposed = false;
|
|
|
|
|
chore: migrate src/ExecutionContext (#5705)
* chore: migrate src/ExecutionContext to TypeScript
I spent a while trying to decide on the best course of action for
typing the `evaluate` function.
Ideally I wanted to use generics so that as a user you could type
something like:
```
handle.evaluate<HTMLElement, number, boolean>((node, x) => true, 5)
```
And have TypeScript know the arguments of `node` and `x` based on those
generics. But I hit two problems with that:
* you have to have n overloads of `evaluate` to cope for as many number
of arguments as you can be bothered too (e.g. we'd need an overload for
1 arg, 2 args, 3 args, etc)
* I decided it's actually confusing because you don't know as a user
what those generics actually map to.
So in the end I went with one generic which is the return type of the
function:
```
handle.evaluate<boolean>((node, x) => true, 5)
```
And `node` and `x` get typed as `any` which means you can tell TS
yourself:
```
handle.evaluate<boolean>((node: HTMLElement, x: number) => true, 5)
```
I'd like to find a way to force that the arguments after the function do
match the arguments you've given (in the above example, TS would moan if
I swapped that `5` for `"foo"`), but I tried a few things and to be
honest the complexity of the types wasn't worth it, I don't think.
I'm very open to tweaking these but I'd rather ship this and tweak going
forwards rather than spend hours now tweaking. Once we ship these
typedefs and get feedback from the community I'm sure we can improve
them.
2020-04-22 09:33:44 +00:00
|
|
|
constructor(context: ExecutionContext, client: CDPSession, remoteObject: Protocol.Runtime.RemoteObject) {
|
2019-01-15 04:34:50 +00:00
|
|
|
this._context = context;
|
|
|
|
this._client = client;
|
|
|
|
this._remoteObject = remoteObject;
|
|
|
|
}
|
|
|
|
|
chore: migrate src/ExecutionContext (#5705)
* chore: migrate src/ExecutionContext to TypeScript
I spent a while trying to decide on the best course of action for
typing the `evaluate` function.
Ideally I wanted to use generics so that as a user you could type
something like:
```
handle.evaluate<HTMLElement, number, boolean>((node, x) => true, 5)
```
And have TypeScript know the arguments of `node` and `x` based on those
generics. But I hit two problems with that:
* you have to have n overloads of `evaluate` to cope for as many number
of arguments as you can be bothered too (e.g. we'd need an overload for
1 arg, 2 args, 3 args, etc)
* I decided it's actually confusing because you don't know as a user
what those generics actually map to.
So in the end I went with one generic which is the return type of the
function:
```
handle.evaluate<boolean>((node, x) => true, 5)
```
And `node` and `x` get typed as `any` which means you can tell TS
yourself:
```
handle.evaluate<boolean>((node: HTMLElement, x: number) => true, 5)
```
I'd like to find a way to force that the arguments after the function do
match the arguments you've given (in the above example, TS would moan if
I swapped that `5` for `"foo"`), but I tried a few things and to be
honest the complexity of the types wasn't worth it, I don't think.
I'm very open to tweaking these but I'd rather ship this and tweak going
forwards rather than spend hours now tweaking. Once we ship these
typedefs and get feedback from the community I'm sure we can improve
them.
2020-04-22 09:33:44 +00:00
|
|
|
executionContext(): ExecutionContext {
|
2019-01-15 04:34:50 +00:00
|
|
|
return this._context;
|
|
|
|
}
|
|
|
|
|
chore: migrate src/ExecutionContext (#5705)
* chore: migrate src/ExecutionContext to TypeScript
I spent a while trying to decide on the best course of action for
typing the `evaluate` function.
Ideally I wanted to use generics so that as a user you could type
something like:
```
handle.evaluate<HTMLElement, number, boolean>((node, x) => true, 5)
```
And have TypeScript know the arguments of `node` and `x` based on those
generics. But I hit two problems with that:
* you have to have n overloads of `evaluate` to cope for as many number
of arguments as you can be bothered too (e.g. we'd need an overload for
1 arg, 2 args, 3 args, etc)
* I decided it's actually confusing because you don't know as a user
what those generics actually map to.
So in the end I went with one generic which is the return type of the
function:
```
handle.evaluate<boolean>((node, x) => true, 5)
```
And `node` and `x` get typed as `any` which means you can tell TS
yourself:
```
handle.evaluate<boolean>((node: HTMLElement, x: number) => true, 5)
```
I'd like to find a way to force that the arguments after the function do
match the arguments you've given (in the above example, TS would moan if
I swapped that `5` for `"foo"`), but I tried a few things and to be
honest the complexity of the types wasn't worth it, I don't think.
I'm very open to tweaking these but I'd rather ship this and tweak going
forwards rather than spend hours now tweaking. Once we ship these
typedefs and get feedback from the community I'm sure we can improve
them.
2020-04-22 09:33:44 +00:00
|
|
|
async evaluate<ReturnType extends any>(pageFunction: Function | string, ...args: unknown[]): Promise<ReturnType> {
|
|
|
|
return await this.executionContext().evaluate<ReturnType>(pageFunction, this, ...args);
|
2019-09-04 22:19:34 +00:00
|
|
|
}
|
|
|
|
|
chore: migrate src/ExecutionContext (#5705)
* chore: migrate src/ExecutionContext to TypeScript
I spent a while trying to decide on the best course of action for
typing the `evaluate` function.
Ideally I wanted to use generics so that as a user you could type
something like:
```
handle.evaluate<HTMLElement, number, boolean>((node, x) => true, 5)
```
And have TypeScript know the arguments of `node` and `x` based on those
generics. But I hit two problems with that:
* you have to have n overloads of `evaluate` to cope for as many number
of arguments as you can be bothered too (e.g. we'd need an overload for
1 arg, 2 args, 3 args, etc)
* I decided it's actually confusing because you don't know as a user
what those generics actually map to.
So in the end I went with one generic which is the return type of the
function:
```
handle.evaluate<boolean>((node, x) => true, 5)
```
And `node` and `x` get typed as `any` which means you can tell TS
yourself:
```
handle.evaluate<boolean>((node: HTMLElement, x: number) => true, 5)
```
I'd like to find a way to force that the arguments after the function do
match the arguments you've given (in the above example, TS would moan if
I swapped that `5` for `"foo"`), but I tried a few things and to be
honest the complexity of the types wasn't worth it, I don't think.
I'm very open to tweaking these but I'd rather ship this and tweak going
forwards rather than spend hours now tweaking. Once we ship these
typedefs and get feedback from the community I'm sure we can improve
them.
2020-04-22 09:33:44 +00:00
|
|
|
async evaluateHandle(pageFunction: Function | string, ...args: unknown[]): Promise<JSHandle> {
|
2019-09-04 22:19:34 +00:00
|
|
|
return await this.executionContext().evaluateHandle(pageFunction, this, ...args);
|
|
|
|
}
|
|
|
|
|
2020-04-21 11:11:06 +00:00
|
|
|
async getProperty(propertyName: string): Promise<JSHandle | undefined> {
|
chore: migrate src/ExecutionContext (#5705)
* chore: migrate src/ExecutionContext to TypeScript
I spent a while trying to decide on the best course of action for
typing the `evaluate` function.
Ideally I wanted to use generics so that as a user you could type
something like:
```
handle.evaluate<HTMLElement, number, boolean>((node, x) => true, 5)
```
And have TypeScript know the arguments of `node` and `x` based on those
generics. But I hit two problems with that:
* you have to have n overloads of `evaluate` to cope for as many number
of arguments as you can be bothered too (e.g. we'd need an overload for
1 arg, 2 args, 3 args, etc)
* I decided it's actually confusing because you don't know as a user
what those generics actually map to.
So in the end I went with one generic which is the return type of the
function:
```
handle.evaluate<boolean>((node, x) => true, 5)
```
And `node` and `x` get typed as `any` which means you can tell TS
yourself:
```
handle.evaluate<boolean>((node: HTMLElement, x: number) => true, 5)
```
I'd like to find a way to force that the arguments after the function do
match the arguments you've given (in the above example, TS would moan if
I swapped that `5` for `"foo"`), but I tried a few things and to be
honest the complexity of the types wasn't worth it, I don't think.
I'm very open to tweaking these but I'd rather ship this and tweak going
forwards rather than spend hours now tweaking. Once we ship these
typedefs and get feedback from the community I'm sure we can improve
them.
2020-04-22 09:33:44 +00:00
|
|
|
const objectHandle = await this.evaluateHandle((object: HTMLElement, propertyName: string) => {
|
2019-01-15 04:34:50 +00:00
|
|
|
const result = {__proto__: null};
|
|
|
|
result[propertyName] = object[propertyName];
|
|
|
|
return result;
|
2019-09-04 22:19:34 +00:00
|
|
|
}, propertyName);
|
2019-01-15 04:34:50 +00:00
|
|
|
const properties = await objectHandle.getProperties();
|
|
|
|
const result = properties.get(propertyName) || null;
|
|
|
|
await objectHandle.dispose();
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2020-04-21 11:11:06 +00:00
|
|
|
async getProperties(): Promise<Map<string, JSHandle>> {
|
2019-01-15 04:34:50 +00:00
|
|
|
const response = await this._client.send('Runtime.getProperties', {
|
|
|
|
objectId: this._remoteObject.objectId,
|
|
|
|
ownProperties: true
|
|
|
|
});
|
2020-04-21 11:11:06 +00:00
|
|
|
const result = new Map<string, JSHandle>();
|
2019-01-15 04:34:50 +00:00
|
|
|
for (const property of response.result) {
|
|
|
|
if (!property.enumerable)
|
|
|
|
continue;
|
|
|
|
result.set(property.name, createJSHandle(this._context, property.value));
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2020-04-21 11:11:06 +00:00
|
|
|
async jsonValue(): Promise<{}> {
|
2019-01-15 04:34:50 +00:00
|
|
|
if (this._remoteObject.objectId) {
|
|
|
|
const response = await this._client.send('Runtime.callFunctionOn', {
|
|
|
|
functionDeclaration: 'function() { return this; }',
|
|
|
|
objectId: this._remoteObject.objectId,
|
|
|
|
returnByValue: true,
|
|
|
|
awaitPromise: true,
|
|
|
|
});
|
|
|
|
return helper.valueFromRemoteObject(response.result);
|
|
|
|
}
|
|
|
|
return helper.valueFromRemoteObject(this._remoteObject);
|
|
|
|
}
|
|
|
|
|
2020-04-21 11:11:06 +00:00
|
|
|
/* This always returns null but children can define this and return an ElementHandle */
|
|
|
|
asElement(): ElementHandle | null {
|
2019-01-15 04:34:50 +00:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2020-04-21 11:11:06 +00:00
|
|
|
async dispose(): Promise<void> {
|
2019-01-15 04:34:50 +00:00
|
|
|
if (this._disposed)
|
|
|
|
return;
|
|
|
|
this._disposed = true;
|
|
|
|
await helper.releaseObject(this._client, this._remoteObject);
|
|
|
|
}
|
|
|
|
|
2020-04-21 11:11:06 +00:00
|
|
|
toString(): string {
|
2019-01-15 04:34:50 +00:00
|
|
|
if (this._remoteObject.objectId) {
|
|
|
|
const type = this._remoteObject.subtype || this._remoteObject.type;
|
|
|
|
return 'JSHandle@' + type;
|
|
|
|
}
|
|
|
|
return 'JSHandle:' + helper.valueFromRemoteObject(this._remoteObject);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-21 11:11:06 +00:00
|
|
|
export class ElementHandle extends JSHandle {
|
|
|
|
_page: Puppeteer.Page;
|
|
|
|
_frameManager: Puppeteer.FrameManager;
|
2019-01-15 04:34:50 +00:00
|
|
|
/**
|
chore: migrate src/ExecutionContext (#5705)
* chore: migrate src/ExecutionContext to TypeScript
I spent a while trying to decide on the best course of action for
typing the `evaluate` function.
Ideally I wanted to use generics so that as a user you could type
something like:
```
handle.evaluate<HTMLElement, number, boolean>((node, x) => true, 5)
```
And have TypeScript know the arguments of `node` and `x` based on those
generics. But I hit two problems with that:
* you have to have n overloads of `evaluate` to cope for as many number
of arguments as you can be bothered too (e.g. we'd need an overload for
1 arg, 2 args, 3 args, etc)
* I decided it's actually confusing because you don't know as a user
what those generics actually map to.
So in the end I went with one generic which is the return type of the
function:
```
handle.evaluate<boolean>((node, x) => true, 5)
```
And `node` and `x` get typed as `any` which means you can tell TS
yourself:
```
handle.evaluate<boolean>((node: HTMLElement, x: number) => true, 5)
```
I'd like to find a way to force that the arguments after the function do
match the arguments you've given (in the above example, TS would moan if
I swapped that `5` for `"foo"`), but I tried a few things and to be
honest the complexity of the types wasn't worth it, I don't think.
I'm very open to tweaking these but I'd rather ship this and tweak going
forwards rather than spend hours now tweaking. Once we ship these
typedefs and get feedback from the community I'm sure we can improve
them.
2020-04-22 09:33:44 +00:00
|
|
|
* @param {!ExecutionContext} context
|
2020-04-21 08:20:25 +00:00
|
|
|
* @param {!CDPSession} client
|
2019-01-15 04:34:50 +00:00
|
|
|
* @param {!Protocol.Runtime.RemoteObject} remoteObject
|
|
|
|
* @param {!Puppeteer.Page} page
|
|
|
|
* @param {!Puppeteer.FrameManager} frameManager
|
|
|
|
*/
|
chore: migrate src/ExecutionContext (#5705)
* chore: migrate src/ExecutionContext to TypeScript
I spent a while trying to decide on the best course of action for
typing the `evaluate` function.
Ideally I wanted to use generics so that as a user you could type
something like:
```
handle.evaluate<HTMLElement, number, boolean>((node, x) => true, 5)
```
And have TypeScript know the arguments of `node` and `x` based on those
generics. But I hit two problems with that:
* you have to have n overloads of `evaluate` to cope for as many number
of arguments as you can be bothered too (e.g. we'd need an overload for
1 arg, 2 args, 3 args, etc)
* I decided it's actually confusing because you don't know as a user
what those generics actually map to.
So in the end I went with one generic which is the return type of the
function:
```
handle.evaluate<boolean>((node, x) => true, 5)
```
And `node` and `x` get typed as `any` which means you can tell TS
yourself:
```
handle.evaluate<boolean>((node: HTMLElement, x: number) => true, 5)
```
I'd like to find a way to force that the arguments after the function do
match the arguments you've given (in the above example, TS would moan if
I swapped that `5` for `"foo"`), but I tried a few things and to be
honest the complexity of the types wasn't worth it, I don't think.
I'm very open to tweaking these but I'd rather ship this and tweak going
forwards rather than spend hours now tweaking. Once we ship these
typedefs and get feedback from the community I'm sure we can improve
them.
2020-04-22 09:33:44 +00:00
|
|
|
constructor(context: ExecutionContext, client: CDPSession, remoteObject: Protocol.Runtime.RemoteObject, page: Puppeteer.Page, frameManager: Puppeteer.FrameManager) {
|
2019-01-15 04:34:50 +00:00
|
|
|
super(context, client, remoteObject);
|
|
|
|
this._client = client;
|
|
|
|
this._remoteObject = remoteObject;
|
|
|
|
this._page = page;
|
|
|
|
this._frameManager = frameManager;
|
|
|
|
}
|
|
|
|
|
2020-04-21 11:11:06 +00:00
|
|
|
asElement(): ElementHandle | null {
|
2019-01-15 04:34:50 +00:00
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
2020-04-21 11:11:06 +00:00
|
|
|
async contentFrame(): Promise<Puppeteer.Frame | null> {
|
2019-01-15 04:34:50 +00:00
|
|
|
const nodeInfo = await this._client.send('DOM.describeNode', {
|
|
|
|
objectId: this._remoteObject.objectId
|
|
|
|
});
|
|
|
|
if (typeof nodeInfo.node.frameId !== 'string')
|
|
|
|
return null;
|
|
|
|
return this._frameManager.frame(nodeInfo.node.frameId);
|
|
|
|
}
|
|
|
|
|
2020-04-21 11:11:06 +00:00
|
|
|
async _scrollIntoViewIfNeeded(): Promise<void> {
|
chore: migrate src/ExecutionContext (#5705)
* chore: migrate src/ExecutionContext to TypeScript
I spent a while trying to decide on the best course of action for
typing the `evaluate` function.
Ideally I wanted to use generics so that as a user you could type
something like:
```
handle.evaluate<HTMLElement, number, boolean>((node, x) => true, 5)
```
And have TypeScript know the arguments of `node` and `x` based on those
generics. But I hit two problems with that:
* you have to have n overloads of `evaluate` to cope for as many number
of arguments as you can be bothered too (e.g. we'd need an overload for
1 arg, 2 args, 3 args, etc)
* I decided it's actually confusing because you don't know as a user
what those generics actually map to.
So in the end I went with one generic which is the return type of the
function:
```
handle.evaluate<boolean>((node, x) => true, 5)
```
And `node` and `x` get typed as `any` which means you can tell TS
yourself:
```
handle.evaluate<boolean>((node: HTMLElement, x: number) => true, 5)
```
I'd like to find a way to force that the arguments after the function do
match the arguments you've given (in the above example, TS would moan if
I swapped that `5` for `"foo"`), but I tried a few things and to be
honest the complexity of the types wasn't worth it, I don't think.
I'm very open to tweaking these but I'd rather ship this and tweak going
forwards rather than spend hours now tweaking. Once we ship these
typedefs and get feedback from the community I'm sure we can improve
them.
2020-04-22 09:33:44 +00:00
|
|
|
const error = await this.evaluate<Promise<string | false>>(async(element: HTMLElement, pageJavascriptEnabled: boolean) => {
|
2019-01-15 04:34:50 +00:00
|
|
|
if (!element.isConnected)
|
|
|
|
return 'Node is detached from document';
|
|
|
|
if (element.nodeType !== Node.ELEMENT_NODE)
|
|
|
|
return 'Node is not of type HTMLElement';
|
|
|
|
// force-scroll if page's javascript is disabled.
|
|
|
|
if (!pageJavascriptEnabled) {
|
chore: migrate src/ExecutionContext (#5705)
* chore: migrate src/ExecutionContext to TypeScript
I spent a while trying to decide on the best course of action for
typing the `evaluate` function.
Ideally I wanted to use generics so that as a user you could type
something like:
```
handle.evaluate<HTMLElement, number, boolean>((node, x) => true, 5)
```
And have TypeScript know the arguments of `node` and `x` based on those
generics. But I hit two problems with that:
* you have to have n overloads of `evaluate` to cope for as many number
of arguments as you can be bothered too (e.g. we'd need an overload for
1 arg, 2 args, 3 args, etc)
* I decided it's actually confusing because you don't know as a user
what those generics actually map to.
So in the end I went with one generic which is the return type of the
function:
```
handle.evaluate<boolean>((node, x) => true, 5)
```
And `node` and `x` get typed as `any` which means you can tell TS
yourself:
```
handle.evaluate<boolean>((node: HTMLElement, x: number) => true, 5)
```
I'd like to find a way to force that the arguments after the function do
match the arguments you've given (in the above example, TS would moan if
I swapped that `5` for `"foo"`), but I tried a few things and to be
honest the complexity of the types wasn't worth it, I don't think.
I'm very open to tweaking these but I'd rather ship this and tweak going
forwards rather than spend hours now tweaking. Once we ship these
typedefs and get feedback from the community I'm sure we can improve
them.
2020-04-22 09:33:44 +00:00
|
|
|
// Chrome still supports behavior: instant but it's not in the spec so TS shouts
|
|
|
|
// We don't want to make this breaking change in Puppeteer yet so we'll ignore the line.
|
|
|
|
// @ts-ignore
|
2019-01-15 04:34:50 +00:00
|
|
|
element.scrollIntoView({block: 'center', inline: 'center', behavior: 'instant'});
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
const visibleRatio = await new Promise(resolve => {
|
|
|
|
const observer = new IntersectionObserver(entries => {
|
|
|
|
resolve(entries[0].intersectionRatio);
|
|
|
|
observer.disconnect();
|
|
|
|
});
|
|
|
|
observer.observe(element);
|
|
|
|
});
|
chore: migrate src/ExecutionContext (#5705)
* chore: migrate src/ExecutionContext to TypeScript
I spent a while trying to decide on the best course of action for
typing the `evaluate` function.
Ideally I wanted to use generics so that as a user you could type
something like:
```
handle.evaluate<HTMLElement, number, boolean>((node, x) => true, 5)
```
And have TypeScript know the arguments of `node` and `x` based on those
generics. But I hit two problems with that:
* you have to have n overloads of `evaluate` to cope for as many number
of arguments as you can be bothered too (e.g. we'd need an overload for
1 arg, 2 args, 3 args, etc)
* I decided it's actually confusing because you don't know as a user
what those generics actually map to.
So in the end I went with one generic which is the return type of the
function:
```
handle.evaluate<boolean>((node, x) => true, 5)
```
And `node` and `x` get typed as `any` which means you can tell TS
yourself:
```
handle.evaluate<boolean>((node: HTMLElement, x: number) => true, 5)
```
I'd like to find a way to force that the arguments after the function do
match the arguments you've given (in the above example, TS would moan if
I swapped that `5` for `"foo"`), but I tried a few things and to be
honest the complexity of the types wasn't worth it, I don't think.
I'm very open to tweaking these but I'd rather ship this and tweak going
forwards rather than spend hours now tweaking. Once we ship these
typedefs and get feedback from the community I'm sure we can improve
them.
2020-04-22 09:33:44 +00:00
|
|
|
if (visibleRatio !== 1.0) {
|
|
|
|
// Chrome still supports behavior: instant but it's not in the spec so TS shouts
|
|
|
|
// We don't want to make this breaking change in Puppeteer yet so we'll ignore the line.
|
|
|
|
// @ts-ignore
|
2019-01-15 04:34:50 +00:00
|
|
|
element.scrollIntoView({block: 'center', inline: 'center', behavior: 'instant'});
|
chore: migrate src/ExecutionContext (#5705)
* chore: migrate src/ExecutionContext to TypeScript
I spent a while trying to decide on the best course of action for
typing the `evaluate` function.
Ideally I wanted to use generics so that as a user you could type
something like:
```
handle.evaluate<HTMLElement, number, boolean>((node, x) => true, 5)
```
And have TypeScript know the arguments of `node` and `x` based on those
generics. But I hit two problems with that:
* you have to have n overloads of `evaluate` to cope for as many number
of arguments as you can be bothered too (e.g. we'd need an overload for
1 arg, 2 args, 3 args, etc)
* I decided it's actually confusing because you don't know as a user
what those generics actually map to.
So in the end I went with one generic which is the return type of the
function:
```
handle.evaluate<boolean>((node, x) => true, 5)
```
And `node` and `x` get typed as `any` which means you can tell TS
yourself:
```
handle.evaluate<boolean>((node: HTMLElement, x: number) => true, 5)
```
I'd like to find a way to force that the arguments after the function do
match the arguments you've given (in the above example, TS would moan if
I swapped that `5` for `"foo"`), but I tried a few things and to be
honest the complexity of the types wasn't worth it, I don't think.
I'm very open to tweaking these but I'd rather ship this and tweak going
forwards rather than spend hours now tweaking. Once we ship these
typedefs and get feedback from the community I'm sure we can improve
them.
2020-04-22 09:33:44 +00:00
|
|
|
}
|
2019-01-15 04:34:50 +00:00
|
|
|
return false;
|
2019-09-04 22:19:34 +00:00
|
|
|
}, this._page._javascriptEnabled);
|
2020-04-21 11:11:06 +00:00
|
|
|
|
2019-01-15 04:34:50 +00:00
|
|
|
if (error)
|
|
|
|
throw new Error(error);
|
|
|
|
}
|
|
|
|
|
2020-04-21 11:11:06 +00:00
|
|
|
async _clickablePoint(): Promise<{x: number; y: number}> {
|
2019-04-12 01:11:20 +00:00
|
|
|
const [result, layoutMetrics] = await Promise.all([
|
|
|
|
this._client.send('DOM.getContentQuads', {
|
|
|
|
objectId: this._remoteObject.objectId
|
|
|
|
}).catch(debugError),
|
|
|
|
this._client.send('Page.getLayoutMetrics'),
|
|
|
|
]);
|
2019-01-15 04:34:50 +00:00
|
|
|
if (!result || !result.quads.length)
|
|
|
|
throw new Error('Node is either not visible or not an HTMLElement');
|
|
|
|
// Filter out quads that have too small area to click into.
|
2019-04-12 01:11:20 +00:00
|
|
|
const {clientWidth, clientHeight} = layoutMetrics.layoutViewport;
|
|
|
|
const quads = result.quads.map(quad => this._fromProtocolQuad(quad)).map(quad => this._intersectQuadWithViewport(quad, clientWidth, clientHeight)).filter(quad => computeQuadArea(quad) > 1);
|
2019-01-15 04:34:50 +00:00
|
|
|
if (!quads.length)
|
|
|
|
throw new Error('Node is either not visible or not an HTMLElement');
|
|
|
|
// Return the middle point of the first quad.
|
|
|
|
const quad = quads[0];
|
|
|
|
let x = 0;
|
|
|
|
let y = 0;
|
|
|
|
for (const point of quad) {
|
|
|
|
x += point.x;
|
|
|
|
y += point.y;
|
|
|
|
}
|
|
|
|
return {
|
|
|
|
x: x / 4,
|
|
|
|
y: y / 4
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2020-04-21 11:11:06 +00:00
|
|
|
_getBoxModel(): Promise<void | Protocol.DOM.getBoxModelReturnValue> {
|
2019-01-15 04:34:50 +00:00
|
|
|
return this._client.send('DOM.getBoxModel', {
|
|
|
|
objectId: this._remoteObject.objectId
|
|
|
|
}).catch(error => debugError(error));
|
|
|
|
}
|
|
|
|
|
2020-04-21 11:11:06 +00:00
|
|
|
_fromProtocolQuad(quad: number[]): Array<{x: number; y: number}> {
|
2019-01-15 04:34:50 +00:00
|
|
|
return [
|
|
|
|
{x: quad[0], y: quad[1]},
|
|
|
|
{x: quad[2], y: quad[3]},
|
|
|
|
{x: quad[4], y: quad[5]},
|
|
|
|
{x: quad[6], y: quad[7]}
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
2019-04-12 01:11:20 +00:00
|
|
|
/**
|
|
|
|
* @param {!Array<{x: number, y: number}>} quad
|
|
|
|
* @param {number} width
|
|
|
|
* @param {number} height
|
|
|
|
* @return {!Array<{x: number, y: number}>}
|
|
|
|
*/
|
2020-04-21 11:11:06 +00:00
|
|
|
_intersectQuadWithViewport(quad: Array<{x: number; y: number}>, width: number, height: number): Array<{x: number; y: number}> {
|
2019-04-12 01:11:20 +00:00
|
|
|
return quad.map(point => ({
|
|
|
|
x: Math.min(Math.max(point.x, 0), width),
|
|
|
|
y: Math.min(Math.max(point.y, 0), height),
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
2020-04-21 11:11:06 +00:00
|
|
|
async hover(): Promise<void> {
|
2019-01-15 04:34:50 +00:00
|
|
|
await this._scrollIntoViewIfNeeded();
|
|
|
|
const {x, y} = await this._clickablePoint();
|
|
|
|
await this._page.mouse.move(x, y);
|
|
|
|
}
|
|
|
|
|
2020-04-21 11:11:06 +00:00
|
|
|
async click(options: {delay?: number; button?: 'left'|'right'|'middle'; clickCount?: number}): Promise<void> {
|
2019-01-15 04:34:50 +00:00
|
|
|
await this._scrollIntoViewIfNeeded();
|
|
|
|
const {x, y} = await this._clickablePoint();
|
|
|
|
await this._page.mouse.click(x, y, options);
|
|
|
|
}
|
|
|
|
|
2019-09-04 22:19:34 +00:00
|
|
|
/**
|
|
|
|
* @param {!Array<string>} values
|
|
|
|
* @return {!Promise<!Array<string>>}
|
|
|
|
*/
|
2020-04-21 11:11:06 +00:00
|
|
|
async select(...values: string[]): Promise<string[]> {
|
2019-09-04 22:19:34 +00:00
|
|
|
for (const value of values)
|
|
|
|
assert(helper.isString(value), 'Values must be strings. Found value "' + value + '" of type "' + (typeof value) + '"');
|
2020-04-21 11:11:06 +00:00
|
|
|
|
|
|
|
/* TODO(jacktfranklin@): once ExecutionContext is TypeScript, and
|
|
|
|
* its evaluate function is properly typed with generics we can
|
|
|
|
* return here and remove the typecasting
|
|
|
|
*/
|
|
|
|
return this.evaluate((element: HTMLSelectElement, values: string[]) => {
|
2019-09-04 22:19:34 +00:00
|
|
|
if (element.nodeName.toLowerCase() !== 'select')
|
|
|
|
throw new Error('Element is not a <select> element.');
|
|
|
|
|
|
|
|
const options = Array.from(element.options);
|
|
|
|
element.value = undefined;
|
|
|
|
for (const option of options) {
|
|
|
|
option.selected = values.includes(option.value);
|
|
|
|
if (option.selected && !element.multiple)
|
|
|
|
break;
|
|
|
|
}
|
2020-04-21 09:40:04 +00:00
|
|
|
element.dispatchEvent(new Event('input', {bubbles: true}));
|
|
|
|
element.dispatchEvent(new Event('change', {bubbles: true}));
|
2019-09-04 22:19:34 +00:00
|
|
|
return options.filter(option => option.selected).map(option => option.value);
|
|
|
|
}, values);
|
|
|
|
}
|
|
|
|
|
2020-04-21 11:11:06 +00:00
|
|
|
async uploadFile(...filePaths: string[]): Promise<void> {
|
chore: migrate src/ExecutionContext (#5705)
* chore: migrate src/ExecutionContext to TypeScript
I spent a while trying to decide on the best course of action for
typing the `evaluate` function.
Ideally I wanted to use generics so that as a user you could type
something like:
```
handle.evaluate<HTMLElement, number, boolean>((node, x) => true, 5)
```
And have TypeScript know the arguments of `node` and `x` based on those
generics. But I hit two problems with that:
* you have to have n overloads of `evaluate` to cope for as many number
of arguments as you can be bothered too (e.g. we'd need an overload for
1 arg, 2 args, 3 args, etc)
* I decided it's actually confusing because you don't know as a user
what those generics actually map to.
So in the end I went with one generic which is the return type of the
function:
```
handle.evaluate<boolean>((node, x) => true, 5)
```
And `node` and `x` get typed as `any` which means you can tell TS
yourself:
```
handle.evaluate<boolean>((node: HTMLElement, x: number) => true, 5)
```
I'd like to find a way to force that the arguments after the function do
match the arguments you've given (in the above example, TS would moan if
I swapped that `5` for `"foo"`), but I tried a few things and to be
honest the complexity of the types wasn't worth it, I don't think.
I'm very open to tweaking these but I'd rather ship this and tweak going
forwards rather than spend hours now tweaking. Once we ship these
typedefs and get feedback from the community I'm sure we can improve
them.
2020-04-22 09:33:44 +00:00
|
|
|
const isMultiple = await this.evaluate<boolean>((element: HTMLInputElement) => element.multiple);
|
2020-01-27 13:44:53 +00:00
|
|
|
assert(filePaths.length <= 1 || isMultiple, 'Multiple file uploads only work with <input type=file multiple>');
|
2020-04-16 15:22:52 +00:00
|
|
|
|
|
|
|
// This import is only needed for `uploadFile`, so keep it scoped here to avoid paying
|
|
|
|
// the cost unnecessarily.
|
2020-04-21 11:11:06 +00:00
|
|
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
2019-12-03 08:18:18 +00:00
|
|
|
const path = require('path');
|
2020-04-16 15:22:52 +00:00
|
|
|
const files = filePaths.map(filePath => path.resolve(filePath));
|
2020-04-21 09:40:04 +00:00
|
|
|
const {objectId} = this._remoteObject;
|
|
|
|
const {node} = await this._client.send('DOM.describeNode', {objectId});
|
|
|
|
const {backendNodeId} = node;
|
2020-04-16 15:22:52 +00:00
|
|
|
|
|
|
|
// The zero-length array is a special case, it seems that DOM.setFileInputFiles does
|
|
|
|
// not actually update the files in that case, so the solution is to eval the element
|
|
|
|
// value to a new FileList directly.
|
|
|
|
if (files.length === 0) {
|
chore: migrate src/ExecutionContext (#5705)
* chore: migrate src/ExecutionContext to TypeScript
I spent a while trying to decide on the best course of action for
typing the `evaluate` function.
Ideally I wanted to use generics so that as a user you could type
something like:
```
handle.evaluate<HTMLElement, number, boolean>((node, x) => true, 5)
```
And have TypeScript know the arguments of `node` and `x` based on those
generics. But I hit two problems with that:
* you have to have n overloads of `evaluate` to cope for as many number
of arguments as you can be bothered too (e.g. we'd need an overload for
1 arg, 2 args, 3 args, etc)
* I decided it's actually confusing because you don't know as a user
what those generics actually map to.
So in the end I went with one generic which is the return type of the
function:
```
handle.evaluate<boolean>((node, x) => true, 5)
```
And `node` and `x` get typed as `any` which means you can tell TS
yourself:
```
handle.evaluate<boolean>((node: HTMLElement, x: number) => true, 5)
```
I'd like to find a way to force that the arguments after the function do
match the arguments you've given (in the above example, TS would moan if
I swapped that `5` for `"foo"`), but I tried a few things and to be
honest the complexity of the types wasn't worth it, I don't think.
I'm very open to tweaking these but I'd rather ship this and tweak going
forwards rather than spend hours now tweaking. Once we ship these
typedefs and get feedback from the community I'm sure we can improve
them.
2020-04-22 09:33:44 +00:00
|
|
|
await this.evaluate((element: HTMLInputElement) => {
|
2020-04-16 15:22:52 +00:00
|
|
|
element.files = new DataTransfer().files;
|
|
|
|
|
|
|
|
// Dispatch events for this case because it should behave akin to a user action.
|
2020-04-21 09:40:04 +00:00
|
|
|
element.dispatchEvent(new Event('input', {bubbles: true}));
|
|
|
|
element.dispatchEvent(new Event('change', {bubbles: true}));
|
2020-04-16 15:22:52 +00:00
|
|
|
});
|
|
|
|
} else {
|
2020-04-21 09:40:04 +00:00
|
|
|
await this._client.send('DOM.setFileInputFiles', {objectId, files, backendNodeId});
|
2019-12-03 08:18:18 +00:00
|
|
|
}
|
2019-01-15 04:34:50 +00:00
|
|
|
}
|
|
|
|
|
2020-04-21 11:11:06 +00:00
|
|
|
async tap(): Promise<void> {
|
2019-01-15 04:34:50 +00:00
|
|
|
await this._scrollIntoViewIfNeeded();
|
|
|
|
const {x, y} = await this._clickablePoint();
|
|
|
|
await this._page.touchscreen.tap(x, y);
|
|
|
|
}
|
|
|
|
|
2020-04-21 11:11:06 +00:00
|
|
|
async focus(): Promise<void> {
|
2019-09-04 22:19:34 +00:00
|
|
|
await this.evaluate(element => element.focus());
|
2019-01-15 04:34:50 +00:00
|
|
|
}
|
|
|
|
|
2020-04-21 11:11:06 +00:00
|
|
|
async type(text: string, options?: {delay: number}): Promise<void> {
|
2019-01-15 04:34:50 +00:00
|
|
|
await this.focus();
|
|
|
|
await this._page.keyboard.type(text, options);
|
|
|
|
}
|
|
|
|
|
2020-04-21 11:11:06 +00:00
|
|
|
async press(key: string, options?: {delay?: number; text?: string}): Promise<void> {
|
2019-01-15 04:34:50 +00:00
|
|
|
await this.focus();
|
|
|
|
await this._page.keyboard.press(key, options);
|
|
|
|
}
|
|
|
|
|
2020-04-21 11:11:06 +00:00
|
|
|
async boundingBox(): Promise<{x: number; y: number; width: number; height: number}> {
|
2019-01-15 04:34:50 +00:00
|
|
|
const result = await this._getBoxModel();
|
|
|
|
|
|
|
|
if (!result)
|
|
|
|
return null;
|
|
|
|
|
|
|
|
const quad = result.model.border;
|
|
|
|
const x = Math.min(quad[0], quad[2], quad[4], quad[6]);
|
|
|
|
const y = Math.min(quad[1], quad[3], quad[5], quad[7]);
|
|
|
|
const width = Math.max(quad[0], quad[2], quad[4], quad[6]) - x;
|
|
|
|
const height = Math.max(quad[1], quad[3], quad[5], quad[7]) - y;
|
|
|
|
|
|
|
|
return {x, y, width, height};
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return {!Promise<?BoxModel>}
|
|
|
|
*/
|
2020-04-21 11:11:06 +00:00
|
|
|
async boxModel(): Promise<BoxModel | null> {
|
2019-01-15 04:34:50 +00:00
|
|
|
const result = await this._getBoxModel();
|
|
|
|
|
|
|
|
if (!result)
|
|
|
|
return null;
|
|
|
|
|
|
|
|
const {content, padding, border, margin, width, height} = result.model;
|
|
|
|
return {
|
|
|
|
content: this._fromProtocolQuad(content),
|
|
|
|
padding: this._fromProtocolQuad(padding),
|
|
|
|
border: this._fromProtocolQuad(border),
|
|
|
|
margin: this._fromProtocolQuad(margin),
|
|
|
|
width,
|
|
|
|
height
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2020-04-21 11:11:06 +00:00
|
|
|
async screenshot(options = {}): Promise<string|Buffer> {
|
2019-01-15 04:34:50 +00:00
|
|
|
let needsViewportReset = false;
|
|
|
|
|
|
|
|
let boundingBox = await this.boundingBox();
|
|
|
|
assert(boundingBox, 'Node is either not visible or not an HTMLElement');
|
|
|
|
|
|
|
|
const viewport = this._page.viewport();
|
|
|
|
|
|
|
|
if (viewport && (boundingBox.width > viewport.width || boundingBox.height > viewport.height)) {
|
|
|
|
const newViewport = {
|
|
|
|
width: Math.max(viewport.width, Math.ceil(boundingBox.width)),
|
|
|
|
height: Math.max(viewport.height, Math.ceil(boundingBox.height)),
|
|
|
|
};
|
|
|
|
await this._page.setViewport(Object.assign({}, viewport, newViewport));
|
|
|
|
|
|
|
|
needsViewportReset = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
await this._scrollIntoViewIfNeeded();
|
|
|
|
|
|
|
|
boundingBox = await this.boundingBox();
|
|
|
|
assert(boundingBox, 'Node is either not visible or not an HTMLElement');
|
|
|
|
assert(boundingBox.width !== 0, 'Node has 0 width.');
|
|
|
|
assert(boundingBox.height !== 0, 'Node has 0 height.');
|
|
|
|
|
2020-04-21 09:40:04 +00:00
|
|
|
const {layoutViewport: {pageX, pageY}} = await this._client.send('Page.getLayoutMetrics');
|
2019-01-15 04:34:50 +00:00
|
|
|
|
|
|
|
const clip = Object.assign({}, boundingBox);
|
|
|
|
clip.x += pageX;
|
|
|
|
clip.y += pageY;
|
|
|
|
|
|
|
|
const imageData = await this._page.screenshot(Object.assign({}, {
|
|
|
|
clip
|
|
|
|
}, options));
|
|
|
|
|
|
|
|
if (needsViewportReset)
|
|
|
|
await this._page.setViewport(viewport);
|
|
|
|
|
|
|
|
return imageData;
|
|
|
|
}
|
|
|
|
|
2020-04-21 11:11:06 +00:00
|
|
|
async $(selector: string): Promise<ElementHandle | null> {
|
2019-09-04 22:19:34 +00:00
|
|
|
const handle = await this.evaluateHandle(
|
2019-01-15 04:34:50 +00:00
|
|
|
(element, selector) => element.querySelector(selector),
|
2019-09-04 22:19:34 +00:00
|
|
|
selector
|
2019-01-15 04:34:50 +00:00
|
|
|
);
|
|
|
|
const element = handle.asElement();
|
|
|
|
if (element)
|
|
|
|
return element;
|
|
|
|
await handle.dispose();
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {string} selector
|
|
|
|
* @return {!Promise<!Array<!ElementHandle>>}
|
|
|
|
*/
|
2020-04-21 11:11:06 +00:00
|
|
|
async $$(selector: string): Promise<ElementHandle[]> {
|
2019-09-04 22:19:34 +00:00
|
|
|
const arrayHandle = await this.evaluateHandle(
|
2019-01-15 04:34:50 +00:00
|
|
|
(element, selector) => element.querySelectorAll(selector),
|
2019-09-04 22:19:34 +00:00
|
|
|
selector
|
2019-01-15 04:34:50 +00:00
|
|
|
);
|
|
|
|
const properties = await arrayHandle.getProperties();
|
|
|
|
await arrayHandle.dispose();
|
|
|
|
const result = [];
|
|
|
|
for (const property of properties.values()) {
|
|
|
|
const elementHandle = property.asElement();
|
|
|
|
if (elementHandle)
|
|
|
|
result.push(elementHandle);
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
chore: migrate src/ExecutionContext (#5705)
* chore: migrate src/ExecutionContext to TypeScript
I spent a while trying to decide on the best course of action for
typing the `evaluate` function.
Ideally I wanted to use generics so that as a user you could type
something like:
```
handle.evaluate<HTMLElement, number, boolean>((node, x) => true, 5)
```
And have TypeScript know the arguments of `node` and `x` based on those
generics. But I hit two problems with that:
* you have to have n overloads of `evaluate` to cope for as many number
of arguments as you can be bothered too (e.g. we'd need an overload for
1 arg, 2 args, 3 args, etc)
* I decided it's actually confusing because you don't know as a user
what those generics actually map to.
So in the end I went with one generic which is the return type of the
function:
```
handle.evaluate<boolean>((node, x) => true, 5)
```
And `node` and `x` get typed as `any` which means you can tell TS
yourself:
```
handle.evaluate<boolean>((node: HTMLElement, x: number) => true, 5)
```
I'd like to find a way to force that the arguments after the function do
match the arguments you've given (in the above example, TS would moan if
I swapped that `5` for `"foo"`), but I tried a few things and to be
honest the complexity of the types wasn't worth it, I don't think.
I'm very open to tweaking these but I'd rather ship this and tweak going
forwards rather than spend hours now tweaking. Once we ship these
typedefs and get feedback from the community I'm sure we can improve
them.
2020-04-22 09:33:44 +00:00
|
|
|
async $eval<ReturnType extends any>(selector: string, pageFunction: Function|string, ...args: unknown[]): Promise<ReturnType> {
|
2019-01-15 04:34:50 +00:00
|
|
|
const elementHandle = await this.$(selector);
|
|
|
|
if (!elementHandle)
|
|
|
|
throw new Error(`Error: failed to find element matching selector "${selector}"`);
|
chore: migrate src/ExecutionContext (#5705)
* chore: migrate src/ExecutionContext to TypeScript
I spent a while trying to decide on the best course of action for
typing the `evaluate` function.
Ideally I wanted to use generics so that as a user you could type
something like:
```
handle.evaluate<HTMLElement, number, boolean>((node, x) => true, 5)
```
And have TypeScript know the arguments of `node` and `x` based on those
generics. But I hit two problems with that:
* you have to have n overloads of `evaluate` to cope for as many number
of arguments as you can be bothered too (e.g. we'd need an overload for
1 arg, 2 args, 3 args, etc)
* I decided it's actually confusing because you don't know as a user
what those generics actually map to.
So in the end I went with one generic which is the return type of the
function:
```
handle.evaluate<boolean>((node, x) => true, 5)
```
And `node` and `x` get typed as `any` which means you can tell TS
yourself:
```
handle.evaluate<boolean>((node: HTMLElement, x: number) => true, 5)
```
I'd like to find a way to force that the arguments after the function do
match the arguments you've given (in the above example, TS would moan if
I swapped that `5` for `"foo"`), but I tried a few things and to be
honest the complexity of the types wasn't worth it, I don't think.
I'm very open to tweaking these but I'd rather ship this and tweak going
forwards rather than spend hours now tweaking. Once we ship these
typedefs and get feedback from the community I'm sure we can improve
them.
2020-04-22 09:33:44 +00:00
|
|
|
const result = await elementHandle.evaluate<ReturnType>(pageFunction, ...args);
|
2019-01-15 04:34:50 +00:00
|
|
|
await elementHandle.dispose();
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
chore: migrate src/ExecutionContext (#5705)
* chore: migrate src/ExecutionContext to TypeScript
I spent a while trying to decide on the best course of action for
typing the `evaluate` function.
Ideally I wanted to use generics so that as a user you could type
something like:
```
handle.evaluate<HTMLElement, number, boolean>((node, x) => true, 5)
```
And have TypeScript know the arguments of `node` and `x` based on those
generics. But I hit two problems with that:
* you have to have n overloads of `evaluate` to cope for as many number
of arguments as you can be bothered too (e.g. we'd need an overload for
1 arg, 2 args, 3 args, etc)
* I decided it's actually confusing because you don't know as a user
what those generics actually map to.
So in the end I went with one generic which is the return type of the
function:
```
handle.evaluate<boolean>((node, x) => true, 5)
```
And `node` and `x` get typed as `any` which means you can tell TS
yourself:
```
handle.evaluate<boolean>((node: HTMLElement, x: number) => true, 5)
```
I'd like to find a way to force that the arguments after the function do
match the arguments you've given (in the above example, TS would moan if
I swapped that `5` for `"foo"`), but I tried a few things and to be
honest the complexity of the types wasn't worth it, I don't think.
I'm very open to tweaking these but I'd rather ship this and tweak going
forwards rather than spend hours now tweaking. Once we ship these
typedefs and get feedback from the community I'm sure we can improve
them.
2020-04-22 09:33:44 +00:00
|
|
|
async $$eval<ReturnType extends any>(selector: string, pageFunction: Function | string, ...args: unknown[]): Promise<ReturnType> {
|
2019-09-04 22:19:34 +00:00
|
|
|
const arrayHandle = await this.evaluateHandle(
|
2019-01-15 04:34:50 +00:00
|
|
|
(element, selector) => Array.from(element.querySelectorAll(selector)),
|
2019-09-04 22:19:34 +00:00
|
|
|
selector
|
2019-01-15 04:34:50 +00:00
|
|
|
);
|
|
|
|
|
chore: migrate src/ExecutionContext (#5705)
* chore: migrate src/ExecutionContext to TypeScript
I spent a while trying to decide on the best course of action for
typing the `evaluate` function.
Ideally I wanted to use generics so that as a user you could type
something like:
```
handle.evaluate<HTMLElement, number, boolean>((node, x) => true, 5)
```
And have TypeScript know the arguments of `node` and `x` based on those
generics. But I hit two problems with that:
* you have to have n overloads of `evaluate` to cope for as many number
of arguments as you can be bothered too (e.g. we'd need an overload for
1 arg, 2 args, 3 args, etc)
* I decided it's actually confusing because you don't know as a user
what those generics actually map to.
So in the end I went with one generic which is the return type of the
function:
```
handle.evaluate<boolean>((node, x) => true, 5)
```
And `node` and `x` get typed as `any` which means you can tell TS
yourself:
```
handle.evaluate<boolean>((node: HTMLElement, x: number) => true, 5)
```
I'd like to find a way to force that the arguments after the function do
match the arguments you've given (in the above example, TS would moan if
I swapped that `5` for `"foo"`), but I tried a few things and to be
honest the complexity of the types wasn't worth it, I don't think.
I'm very open to tweaking these but I'd rather ship this and tweak going
forwards rather than spend hours now tweaking. Once we ship these
typedefs and get feedback from the community I'm sure we can improve
them.
2020-04-22 09:33:44 +00:00
|
|
|
const result = await arrayHandle.evaluate<ReturnType>(pageFunction, ...args);
|
2019-01-15 04:34:50 +00:00
|
|
|
await arrayHandle.dispose();
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2020-04-21 11:11:06 +00:00
|
|
|
async $x(expression: string): Promise<ElementHandle[]> {
|
2019-09-04 22:19:34 +00:00
|
|
|
const arrayHandle = await this.evaluateHandle(
|
2019-01-15 04:34:50 +00:00
|
|
|
(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;
|
|
|
|
},
|
2019-09-04 22:19:34 +00:00
|
|
|
expression
|
2019-01-15 04:34:50 +00:00
|
|
|
);
|
|
|
|
const properties = await arrayHandle.getProperties();
|
|
|
|
await arrayHandle.dispose();
|
|
|
|
const result = [];
|
|
|
|
for (const property of properties.values()) {
|
|
|
|
const elementHandle = property.asElement();
|
|
|
|
if (elementHandle)
|
|
|
|
result.push(elementHandle);
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
chore: migrate src/ExecutionContext (#5705)
* chore: migrate src/ExecutionContext to TypeScript
I spent a while trying to decide on the best course of action for
typing the `evaluate` function.
Ideally I wanted to use generics so that as a user you could type
something like:
```
handle.evaluate<HTMLElement, number, boolean>((node, x) => true, 5)
```
And have TypeScript know the arguments of `node` and `x` based on those
generics. But I hit two problems with that:
* you have to have n overloads of `evaluate` to cope for as many number
of arguments as you can be bothered too (e.g. we'd need an overload for
1 arg, 2 args, 3 args, etc)
* I decided it's actually confusing because you don't know as a user
what those generics actually map to.
So in the end I went with one generic which is the return type of the
function:
```
handle.evaluate<boolean>((node, x) => true, 5)
```
And `node` and `x` get typed as `any` which means you can tell TS
yourself:
```
handle.evaluate<boolean>((node: HTMLElement, x: number) => true, 5)
```
I'd like to find a way to force that the arguments after the function do
match the arguments you've given (in the above example, TS would moan if
I swapped that `5` for `"foo"`), but I tried a few things and to be
honest the complexity of the types wasn't worth it, I don't think.
I'm very open to tweaking these but I'd rather ship this and tweak going
forwards rather than spend hours now tweaking. Once we ship these
typedefs and get feedback from the community I'm sure we can improve
them.
2020-04-22 09:33:44 +00:00
|
|
|
async isIntersectingViewport(): Promise<boolean> {
|
|
|
|
return await this.evaluate<Promise<boolean>>(async element => {
|
2019-01-15 04:34:50 +00:00
|
|
|
const visibleRatio = await new Promise(resolve => {
|
|
|
|
const observer = new IntersectionObserver(entries => {
|
|
|
|
resolve(entries[0].intersectionRatio);
|
|
|
|
observer.disconnect();
|
|
|
|
});
|
|
|
|
observer.observe(element);
|
|
|
|
});
|
|
|
|
return visibleRatio > 0;
|
2019-09-04 22:19:34 +00:00
|
|
|
});
|
2019-01-15 04:34:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-21 11:11:06 +00:00
|
|
|
function computeQuadArea(quad: Array<{x: number; y: number}>): number {
|
2019-01-15 04:34:50 +00:00
|
|
|
// Compute sum of all directed areas of adjacent triangles
|
|
|
|
// https://en.wikipedia.org/wiki/Polygon#Simple_polygons
|
|
|
|
let area = 0;
|
|
|
|
for (let i = 0; i < quad.length; ++i) {
|
|
|
|
const p1 = quad[i];
|
|
|
|
const p2 = quad[(i + 1) % quad.length];
|
|
|
|
area += (p1.x * p2.y - p2.x * p1.y) / 2;
|
|
|
|
}
|
|
|
|
return Math.abs(area);
|
|
|
|
}
|