chore: migrate src/JSHandle to TS (#5703)

* chore: migrate src/JSHandle to TS

There's a few TODOs in here that all depend on typing the
`ExecutionContext.evaluateHandle` properly so that you can properly
declare what types you're expecting back. Once I've done that file (it's
next on my list) I will loop back and improve the types here, fixing
these TODOs.

* Fix doclint for {}
This commit is contained in:
Jack Franklin 2020-04-21 12:11:06 +01:00 committed by GitHub
parent 42893d8755
commit 8d5d76ed70
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 131 additions and 207 deletions

View File

@ -17,6 +17,9 @@
// Used as a TypeDef // Used as a TypeDef
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
const {CDPSession} = require('./Connection'); const {CDPSession} = require('./Connection');
// Used as a TypeDef
// eslint-disable-next-line no-unused-vars
const {ElementHandle} = require('./JSHandle');
/** /**
* @typedef {Object} SerializedAXNode * @typedef {Object} SerializedAXNode
@ -64,7 +67,7 @@ class Accessibility {
} }
/** /**
* @param {{interestingOnly?: boolean, root?: ?Puppeteer.ElementHandle}=} options * @param {{interestingOnly?: boolean, root?: ?ElementHandle}=} options
* @return {!Promise<!SerializedAXNode>} * @return {!Promise<!SerializedAXNode>}
*/ */
async snapshot(options = {}) { async snapshot(options = {}) {

View File

@ -18,6 +18,9 @@ const fs = require('fs');
const {helper, assert} = require('./helper'); const {helper, assert} = require('./helper');
const {LifecycleWatcher} = require('./LifecycleWatcher'); const {LifecycleWatcher} = require('./LifecycleWatcher');
const {TimeoutError} = require('./Errors'); const {TimeoutError} = require('./Errors');
// Used as a TypeDef
// eslint-disable-next-line no-unused-vars
const {JSHandle, ElementHandle} = require('./JSHandle');
// Used as a TypeDef // Used as a TypeDef
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
@ -39,7 +42,7 @@ class DOMWorld {
this._frame = frame; this._frame = frame;
this._timeoutSettings = timeoutSettings; this._timeoutSettings = timeoutSettings;
/** @type {?Promise<!Puppeteer.ElementHandle>} */ /** @type {?Promise<!ElementHandle>} */
this._documentPromise = null; this._documentPromise = null;
/** @type {!Promise<!Puppeteer.ExecutionContext>} */ /** @type {!Promise<!Puppeteer.ExecutionContext>} */
this._contextPromise; this._contextPromise;
@ -100,7 +103,7 @@ class DOMWorld {
/** /**
* @param {Function|string} pageFunction * @param {Function|string} pageFunction
* @param {!Array<*>} args * @param {!Array<*>} args
* @return {!Promise<!Puppeteer.JSHandle>} * @return {!Promise<!JSHandle>}
*/ */
async evaluateHandle(pageFunction, ...args) { async evaluateHandle(pageFunction, ...args) {
const context = await this.executionContext(); const context = await this.executionContext();
@ -119,7 +122,7 @@ class DOMWorld {
/** /**
* @param {string} selector * @param {string} selector
* @return {!Promise<?Puppeteer.ElementHandle>} * @return {!Promise<?ElementHandle>}
*/ */
async $(selector) { async $(selector) {
const document = await this._document(); const document = await this._document();
@ -128,7 +131,7 @@ class DOMWorld {
} }
/** /**
* @return {!Promise<!Puppeteer.ElementHandle>} * @return {!Promise<!ElementHandle>}
*/ */
async _document() { async _document() {
if (this._documentPromise) if (this._documentPromise)
@ -142,7 +145,7 @@ class DOMWorld {
/** /**
* @param {string} expression * @param {string} expression
* @return {!Promise<!Array<!Puppeteer.ElementHandle>>} * @return {!Promise<!Array<!ElementHandle>>}
*/ */
async $x(expression) { async $x(expression) {
const document = await this._document(); const document = await this._document();
@ -175,7 +178,7 @@ class DOMWorld {
/** /**
* @param {string} selector * @param {string} selector
* @return {!Promise<!Array<!Puppeteer.ElementHandle>>} * @return {!Promise<!Array<!ElementHandle>>}
*/ */
async $$(selector) { async $$(selector) {
const document = await this._document(); const document = await this._document();
@ -225,7 +228,7 @@ class DOMWorld {
/** /**
* @param {!{url?: string, path?: string, content?: string, type?: string}} options * @param {!{url?: string, path?: string, content?: string, type?: string}} options
* @return {!Promise<!Puppeteer.ElementHandle>} * @return {!Promise<!ElementHandle>}
*/ */
async addScriptTag(options) { async addScriptTag(options) {
const { const {
@ -296,7 +299,7 @@ class DOMWorld {
/** /**
* @param {!{url?: string, path?: string, content?: string}} options * @param {!{url?: string, path?: string, content?: string}} options
* @return {!Promise<!Puppeteer.ElementHandle>} * @return {!Promise<!ElementHandle>}
*/ */
async addStyleTag(options) { async addStyleTag(options) {
const { const {
@ -431,7 +434,7 @@ class DOMWorld {
/** /**
* @param {string} selector * @param {string} selector
* @param {!{visible?: boolean, hidden?: boolean, timeout?: number}=} options * @param {!{visible?: boolean, hidden?: boolean, timeout?: number}=} options
* @return {!Promise<?Puppeteer.ElementHandle>} * @return {!Promise<?ElementHandle>}
*/ */
waitForSelector(selector, options) { waitForSelector(selector, options) {
return this._waitForSelectorOrXPath(selector, false, options); return this._waitForSelectorOrXPath(selector, false, options);
@ -440,7 +443,7 @@ class DOMWorld {
/** /**
* @param {string} xpath * @param {string} xpath
* @param {!{visible?: boolean, hidden?: boolean, timeout?: number}=} options * @param {!{visible?: boolean, hidden?: boolean, timeout?: number}=} options
* @return {!Promise<?Puppeteer.ElementHandle>} * @return {!Promise<?ElementHandle>}
*/ */
waitForXPath(xpath, options) { waitForXPath(xpath, options) {
return this._waitForSelectorOrXPath(xpath, true, options); return this._waitForSelectorOrXPath(xpath, true, options);
@ -449,7 +452,7 @@ class DOMWorld {
/** /**
* @param {Function|string} pageFunction * @param {Function|string} pageFunction
* @param {!{polling?: string|number, timeout?: number}=} options * @param {!{polling?: string|number, timeout?: number}=} options
* @return {!Promise<!Puppeteer.JSHandle>} * @return {!Promise<!JSHandle>}
*/ */
waitForFunction(pageFunction, options = {}, ...args) { waitForFunction(pageFunction, options = {}, ...args) {
const { const {
@ -470,7 +473,7 @@ class DOMWorld {
* @param {string} selectorOrXPath * @param {string} selectorOrXPath
* @param {boolean} isXPath * @param {boolean} isXPath
* @param {!{visible?: boolean, hidden?: boolean, timeout?: number}=} options * @param {!{visible?: boolean, hidden?: boolean, timeout?: number}=} options
* @return {!Promise<?Puppeteer.ElementHandle>} * @return {!Promise<?ElementHandle>}
*/ */
async _waitForSelectorOrXPath(selectorOrXPath, isXPath, options = {}) { async _waitForSelectorOrXPath(selectorOrXPath, isXPath, options = {}) {
const { const {
@ -568,7 +571,7 @@ class WaitTask {
async rerun() { async rerun() {
const runCount = ++this._runCount; const runCount = ++this._runCount;
/** @type {?Puppeteer.JSHandle} */ /** @type {?JSHandle} */
let success = null; let success = null;
let error = null; let error = null;
try { try {

View File

@ -15,10 +15,12 @@
*/ */
const {helper, assert} = require('./helper'); const {helper, assert} = require('./helper');
const {createJSHandle, JSHandle} = require('./JSHandle');
// Used as a TypeDef // Used as a TypeDef
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
const {CDPSession} = require('./Connection'); const {CDPSession} = require('./Connection');
// Used as a TypeDef
// eslint-disable-next-line no-unused-vars
const {createJSHandle, JSHandle, ElementHandle} = require('./JSHandle');
const EVALUATION_SCRIPT_URL = '__puppeteer_evaluation_script__'; const EVALUATION_SCRIPT_URL = '__puppeteer_evaluation_script__';
const SOURCE_URL_REGEX = /^[\040\t]*\/\/[@#] sourceURL=\s*(\S*?)\s*$/m; const SOURCE_URL_REGEX = /^[\040\t]*\/\/[@#] sourceURL=\s*(\S*?)\s*$/m;
@ -187,19 +189,19 @@ class ExecutionContext {
/** /**
* @param {Protocol.DOM.BackendNodeId} backendNodeId * @param {Protocol.DOM.BackendNodeId} backendNodeId
* @return {Promise<Puppeteer.ElementHandle>} * @return {Promise<ElementHandle>}
*/ */
async _adoptBackendNodeId(backendNodeId) { async _adoptBackendNodeId(backendNodeId) {
const {object} = await this._client.send('DOM.resolveNode', { const {object} = await this._client.send('DOM.resolveNode', {
backendNodeId: backendNodeId, backendNodeId: backendNodeId,
executionContextId: this._contextId, executionContextId: this._contextId,
}); });
return /** @type {Puppeteer.ElementHandle}*/(createJSHandle(this, object)); return /** @type {ElementHandle}*/(createJSHandle(this, object));
} }
/** /**
* @param {Puppeteer.ElementHandle} elementHandle * @param {ElementHandle} elementHandle
* @return {Promise<Puppeteer.ElementHandle>} * @return {Promise<ElementHandle>}
*/ */
async _adoptElementHandle(elementHandle) { async _adoptElementHandle(elementHandle) {
assert(elementHandle.executionContext() !== this, 'Cannot adopt handle that already belongs to this execution context'); assert(elementHandle.executionContext() !== this, 'Cannot adopt handle that already belongs to this execution context');

View File

@ -27,6 +27,9 @@ const {TimeoutSettings} = require('./TimeoutSettings');
// Used as a TypeDef // Used as a TypeDef
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
const {CDPSession} = require('./Connection'); const {CDPSession} = require('./Connection');
// Used as a TypeDef
// eslint-disable-next-line no-unused-vars
const {JSHandle, ElementHandle} = require('./JSHandle');
const UTILITY_WORLD_NAME = '__puppeteer_utility_world__'; const UTILITY_WORLD_NAME = '__puppeteer_utility_world__';
@ -432,7 +435,7 @@ class Frame {
/** /**
* @param {Function|string} pageFunction * @param {Function|string} pageFunction
* @param {!Array<*>} args * @param {!Array<*>} args
* @return {!Promise<!Puppeteer.JSHandle>} * @return {!Promise<!JSHandle>}
*/ */
async evaluateHandle(pageFunction, ...args) { async evaluateHandle(pageFunction, ...args) {
return this._mainWorld.evaluateHandle(pageFunction, ...args); return this._mainWorld.evaluateHandle(pageFunction, ...args);
@ -449,7 +452,7 @@ class Frame {
/** /**
* @param {string} selector * @param {string} selector
* @return {!Promise<?Puppeteer.ElementHandle>} * @return {!Promise<?ElementHandle>}
*/ */
async $(selector) { async $(selector) {
return this._mainWorld.$(selector); return this._mainWorld.$(selector);
@ -457,7 +460,7 @@ class Frame {
/** /**
* @param {string} expression * @param {string} expression
* @return {!Promise<!Array<!Puppeteer.ElementHandle>>} * @return {!Promise<!Array<!ElementHandle>>}
*/ */
async $x(expression) { async $x(expression) {
return this._mainWorld.$x(expression); return this._mainWorld.$x(expression);
@ -485,7 +488,7 @@ class Frame {
/** /**
* @param {string} selector * @param {string} selector
* @return {!Promise<!Array<!Puppeteer.ElementHandle>>} * @return {!Promise<!Array<!ElementHandle>>}
*/ */
async $$(selector) { async $$(selector) {
return this._mainWorld.$$(selector); return this._mainWorld.$$(selector);
@ -543,7 +546,7 @@ class Frame {
/** /**
* @param {!{url?: string, path?: string, content?: string, type?: string}} options * @param {!{url?: string, path?: string, content?: string, type?: string}} options
* @return {!Promise<!Puppeteer.ElementHandle>} * @return {!Promise<!ElementHandle>}
*/ */
async addScriptTag(options) { async addScriptTag(options) {
return this._mainWorld.addScriptTag(options); return this._mainWorld.addScriptTag(options);
@ -551,7 +554,7 @@ class Frame {
/** /**
* @param {!{url?: string, path?: string, content?: string}} options * @param {!{url?: string, path?: string, content?: string}} options
* @return {!Promise<!Puppeteer.ElementHandle>} * @return {!Promise<!ElementHandle>}
*/ */
async addStyleTag(options) { async addStyleTag(options) {
return this._mainWorld.addStyleTag(options); return this._mainWorld.addStyleTag(options);
@ -608,7 +611,7 @@ class Frame {
* @param {(string|number|Function)} selectorOrFunctionOrTimeout * @param {(string|number|Function)} selectorOrFunctionOrTimeout
* @param {!Object=} options * @param {!Object=} options
* @param {!Array<*>} args * @param {!Array<*>} args
* @return {!Promise<?Puppeteer.JSHandle>} * @return {!Promise<?JSHandle>}
*/ */
waitFor(selectorOrFunctionOrTimeout, options = {}, ...args) { waitFor(selectorOrFunctionOrTimeout, options = {}, ...args) {
const xPathPattern = '//'; const xPathPattern = '//';
@ -629,7 +632,7 @@ class Frame {
/** /**
* @param {string} selector * @param {string} selector
* @param {!{visible?: boolean, hidden?: boolean, timeout?: number}=} options * @param {!{visible?: boolean, hidden?: boolean, timeout?: number}=} options
* @return {!Promise<?Puppeteer.ElementHandle>} * @return {!Promise<?ElementHandle>}
*/ */
async waitForSelector(selector, options) { async waitForSelector(selector, options) {
const handle = await this._secondaryWorld.waitForSelector(selector, options); const handle = await this._secondaryWorld.waitForSelector(selector, options);
@ -644,7 +647,7 @@ class Frame {
/** /**
* @param {string} xpath * @param {string} xpath
* @param {!{visible?: boolean, hidden?: boolean, timeout?: number}=} options * @param {!{visible?: boolean, hidden?: boolean, timeout?: number}=} options
* @return {!Promise<?Puppeteer.ElementHandle>} * @return {!Promise<?ElementHandle>}
*/ */
async waitForXPath(xpath, options) { async waitForXPath(xpath, options) {
const handle = await this._secondaryWorld.waitForXPath(xpath, options); const handle = await this._secondaryWorld.waitForXPath(xpath, options);
@ -659,7 +662,7 @@ class Frame {
/** /**
* @param {Function|string} pageFunction * @param {Function|string} pageFunction
* @param {!{polling?: string|number, timeout?: number}=} options * @param {!{polling?: string|number, timeout?: number}=} options
* @return {!Promise<!Puppeteer.JSHandle>} * @return {!Promise<!JSHandle>}
*/ */
waitForFunction(pageFunction, options = {}, ...args) { waitForFunction(pageFunction, options = {}, ...args) {
return this._mainWorld.waitForFunction(pageFunction, options, ...args); return this._mainWorld.waitForFunction(pageFunction, options, ...args);

View File

@ -14,12 +14,19 @@
* limitations under the License. * limitations under the License.
*/ */
const {helper, assert, debugError} = require('./helper'); import {helper, assert, debugError} from './helper';
// CDPSession is used only as a typedef import {CDPSession} from './Connection';
// eslint-disable-next-line no-unused-vars
const {CDPSession} = require('./Connection');
function createJSHandle(context, remoteObject) { 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;
}
export function createJSHandle(context: Puppeteer.ExecutionContext, remoteObject: Protocol.Runtime.RemoteObject): JSHandle {
const frame = context.frame(); const frame = context.frame();
if (remoteObject.subtype === 'node' && frame) { if (remoteObject.subtype === 'node' && frame) {
const frameManager = frame._frameManager; const frameManager = frame._frameManager;
@ -28,49 +35,31 @@ function createJSHandle(context, remoteObject) {
return new JSHandle(context, context._client, remoteObject); return new JSHandle(context, context._client, remoteObject);
} }
class JSHandle { export class JSHandle {
/** _context: Puppeteer.ExecutionContext;
* @param {!Puppeteer.ExecutionContext} context _client: CDPSession;
* @param {!CDPSession} client _remoteObject: Protocol.Runtime.RemoteObject;
* @param {!Protocol.Runtime.RemoteObject} remoteObject _disposed = false;
*/
constructor(context, client, remoteObject) { constructor(context: Puppeteer.ExecutionContext, client: CDPSession, remoteObject: Protocol.Runtime.RemoteObject) {
this._context = context; this._context = context;
this._client = client; this._client = client;
this._remoteObject = remoteObject; this._remoteObject = remoteObject;
this._disposed = false;
} }
/** executionContext(): Puppeteer.ExecutionContext {
* @return {!Puppeteer.ExecutionContext}
*/
executionContext() {
return this._context; return this._context;
} }
/** async evaluate<T extends any>(pageFunction: Function | string, ...args: any[]): Promise<T | undefined> {
* @param {Function|String} pageFunction
* @param {!Array<*>} args
* @return {!Promise<(!Object|undefined)>}
*/
async evaluate(pageFunction, ...args) {
return await this.executionContext().evaluate(pageFunction, this, ...args); return await this.executionContext().evaluate(pageFunction, this, ...args);
} }
/** async evaluateHandle(pageFunction: Function | string, ...args: any[]): Promise<JSHandle> {
* @param {Function|string} pageFunction
* @param {!Array<*>} args
* @return {!Promise<!Puppeteer.JSHandle>}
*/
async evaluateHandle(pageFunction, ...args) {
return await this.executionContext().evaluateHandle(pageFunction, this, ...args); return await this.executionContext().evaluateHandle(pageFunction, this, ...args);
} }
/** async getProperty(propertyName: string): Promise<JSHandle | undefined> {
* @param {string} propertyName
* @return {!Promise<?JSHandle>}
*/
async getProperty(propertyName) {
const objectHandle = await this.evaluateHandle((object, propertyName) => { const objectHandle = await this.evaluateHandle((object, propertyName) => {
const result = {__proto__: null}; const result = {__proto__: null};
result[propertyName] = object[propertyName]; result[propertyName] = object[propertyName];
@ -82,15 +71,12 @@ class JSHandle {
return result; return result;
} }
/** async getProperties(): Promise<Map<string, JSHandle>> {
* @return {!Promise<!Map<string, !JSHandle>>}
*/
async getProperties() {
const response = await this._client.send('Runtime.getProperties', { const response = await this._client.send('Runtime.getProperties', {
objectId: this._remoteObject.objectId, objectId: this._remoteObject.objectId,
ownProperties: true ownProperties: true
}); });
const result = new Map(); const result = new Map<string, JSHandle>();
for (const property of response.result) { for (const property of response.result) {
if (!property.enumerable) if (!property.enumerable)
continue; continue;
@ -99,10 +85,7 @@ class JSHandle {
return result; return result;
} }
/** async jsonValue(): Promise<{}> {
* @return {!Promise<?Object>}
*/
async jsonValue() {
if (this._remoteObject.objectId) { if (this._remoteObject.objectId) {
const response = await this._client.send('Runtime.callFunctionOn', { const response = await this._client.send('Runtime.callFunctionOn', {
functionDeclaration: 'function() { return this; }', functionDeclaration: 'function() { return this; }',
@ -115,25 +98,19 @@ class JSHandle {
return helper.valueFromRemoteObject(this._remoteObject); return helper.valueFromRemoteObject(this._remoteObject);
} }
/** /* This always returns null but children can define this and return an ElementHandle */
* @return {?Puppeteer.ElementHandle} asElement(): ElementHandle | null {
*/
asElement() {
return null; return null;
} }
async dispose() { async dispose(): Promise<void> {
if (this._disposed) if (this._disposed)
return; return;
this._disposed = true; this._disposed = true;
await helper.releaseObject(this._client, this._remoteObject); await helper.releaseObject(this._client, this._remoteObject);
} }
/** toString(): string {
* @override
* @return {string}
*/
toString() {
if (this._remoteObject.objectId) { if (this._remoteObject.objectId) {
const type = this._remoteObject.subtype || this._remoteObject.type; const type = this._remoteObject.subtype || this._remoteObject.type;
return 'JSHandle@' + type; return 'JSHandle@' + type;
@ -142,7 +119,9 @@ class JSHandle {
} }
} }
class ElementHandle extends JSHandle { export class ElementHandle extends JSHandle {
_page: Puppeteer.Page;
_frameManager: Puppeteer.FrameManager;
/** /**
* @param {!Puppeteer.ExecutionContext} context * @param {!Puppeteer.ExecutionContext} context
* @param {!CDPSession} client * @param {!CDPSession} client
@ -150,27 +129,19 @@ class ElementHandle extends JSHandle {
* @param {!Puppeteer.Page} page * @param {!Puppeteer.Page} page
* @param {!Puppeteer.FrameManager} frameManager * @param {!Puppeteer.FrameManager} frameManager
*/ */
constructor(context, client, remoteObject, page, frameManager) { constructor(context: Puppeteer.ExecutionContext, client: CDPSession, remoteObject: Protocol.Runtime.RemoteObject, page: Puppeteer.Page, frameManager: Puppeteer.FrameManager) {
super(context, client, remoteObject); super(context, client, remoteObject);
this._client = client; this._client = client;
this._remoteObject = remoteObject; this._remoteObject = remoteObject;
this._page = page; this._page = page;
this._frameManager = frameManager; this._frameManager = frameManager;
this._disposed = false;
} }
/** asElement(): ElementHandle | null {
* @override
* @return {?ElementHandle}
*/
asElement() {
return this; return this;
} }
/** async contentFrame(): Promise<Puppeteer.Frame | null> {
* @return {!Promise<?Puppeteer.Frame>}
*/
async contentFrame() {
const nodeInfo = await this._client.send('DOM.describeNode', { const nodeInfo = await this._client.send('DOM.describeNode', {
objectId: this._remoteObject.objectId objectId: this._remoteObject.objectId
}); });
@ -179,8 +150,8 @@ class ElementHandle extends JSHandle {
return this._frameManager.frame(nodeInfo.node.frameId); return this._frameManager.frame(nodeInfo.node.frameId);
} }
async _scrollIntoViewIfNeeded() { async _scrollIntoViewIfNeeded(): Promise<void> {
const error = await this.evaluate(async(element, pageJavascriptEnabled) => { const error = await this.evaluate<string | false>(async(element, pageJavascriptEnabled) => {
if (!element.isConnected) if (!element.isConnected)
return 'Node is detached from document'; return 'Node is detached from document';
if (element.nodeType !== Node.ELEMENT_NODE) if (element.nodeType !== Node.ELEMENT_NODE)
@ -201,14 +172,12 @@ class ElementHandle extends JSHandle {
element.scrollIntoView({block: 'center', inline: 'center', behavior: 'instant'}); element.scrollIntoView({block: 'center', inline: 'center', behavior: 'instant'});
return false; return false;
}, this._page._javascriptEnabled); }, this._page._javascriptEnabled);
if (error) if (error)
throw new Error(error); throw new Error(error);
} }
/** async _clickablePoint(): Promise<{x: number; y: number}> {
* @return {!Promise<!{x: number, y: number}>}
*/
async _clickablePoint() {
const [result, layoutMetrics] = await Promise.all([ const [result, layoutMetrics] = await Promise.all([
this._client.send('DOM.getContentQuads', { this._client.send('DOM.getContentQuads', {
objectId: this._remoteObject.objectId objectId: this._remoteObject.objectId
@ -236,20 +205,13 @@ class ElementHandle extends JSHandle {
}; };
} }
/** _getBoxModel(): Promise<void | Protocol.DOM.getBoxModelReturnValue> {
* @return {!Promise<void|Protocol.DOM.getBoxModelReturnValue>}
*/
_getBoxModel() {
return this._client.send('DOM.getBoxModel', { return this._client.send('DOM.getBoxModel', {
objectId: this._remoteObject.objectId objectId: this._remoteObject.objectId
}).catch(error => debugError(error)); }).catch(error => debugError(error));
} }
/** _fromProtocolQuad(quad: number[]): Array<{x: number; y: number}> {
* @param {!Array<number>} quad
* @return {!Array<{x: number, y: number}>}
*/
_fromProtocolQuad(quad) {
return [ return [
{x: quad[0], y: quad[1]}, {x: quad[0], y: quad[1]},
{x: quad[2], y: quad[3]}, {x: quad[2], y: quad[3]},
@ -264,23 +226,20 @@ class ElementHandle extends JSHandle {
* @param {number} height * @param {number} height
* @return {!Array<{x: number, y: number}>} * @return {!Array<{x: number, y: number}>}
*/ */
_intersectQuadWithViewport(quad, width, height) { _intersectQuadWithViewport(quad: Array<{x: number; y: number}>, width: number, height: number): Array<{x: number; y: number}> {
return quad.map(point => ({ return quad.map(point => ({
x: Math.min(Math.max(point.x, 0), width), x: Math.min(Math.max(point.x, 0), width),
y: Math.min(Math.max(point.y, 0), height), y: Math.min(Math.max(point.y, 0), height),
})); }));
} }
async hover() { async hover(): Promise<void> {
await this._scrollIntoViewIfNeeded(); await this._scrollIntoViewIfNeeded();
const {x, y} = await this._clickablePoint(); const {x, y} = await this._clickablePoint();
await this._page.mouse.move(x, y); await this._page.mouse.move(x, y);
} }
/** async click(options: {delay?: number; button?: 'left'|'right'|'middle'; clickCount?: number}): Promise<void> {
* @param {!{delay?: number, button?: "left"|"right"|"middle", clickCount?: number}=} options
*/
async click(options) {
await this._scrollIntoViewIfNeeded(); await this._scrollIntoViewIfNeeded();
const {x, y} = await this._clickablePoint(); const {x, y} = await this._clickablePoint();
await this._page.mouse.click(x, y, options); await this._page.mouse.click(x, y, options);
@ -290,10 +249,15 @@ class ElementHandle extends JSHandle {
* @param {!Array<string>} values * @param {!Array<string>} values
* @return {!Promise<!Array<string>>} * @return {!Promise<!Array<string>>}
*/ */
async select(...values) { async select(...values: string[]): Promise<string[]> {
for (const value of values) for (const value of values)
assert(helper.isString(value), 'Values must be strings. Found value "' + value + '" of type "' + (typeof value) + '"'); assert(helper.isString(value), 'Values must be strings. Found value "' + value + '" of type "' + (typeof value) + '"');
return this.evaluate((element, values) => {
/* 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[]) => {
if (element.nodeName.toLowerCase() !== 'select') if (element.nodeName.toLowerCase() !== 'select')
throw new Error('Element is not a <select> element.'); throw new Error('Element is not a <select> element.');
@ -310,15 +274,13 @@ class ElementHandle extends JSHandle {
}, values); }, values);
} }
/** async uploadFile(...filePaths: string[]): Promise<void> {
* @param {!Array<string>} filePaths
*/
async uploadFile(...filePaths) {
const isMultiple = await this.evaluate(element => element.multiple); const isMultiple = await this.evaluate(element => element.multiple);
assert(filePaths.length <= 1 || isMultiple, 'Multiple file uploads only work with <input type=file multiple>'); assert(filePaths.length <= 1 || isMultiple, 'Multiple file uploads only work with <input type=file multiple>');
// This import is only needed for `uploadFile`, so keep it scoped here to avoid paying // This import is only needed for `uploadFile`, so keep it scoped here to avoid paying
// the cost unnecessarily. // the cost unnecessarily.
// eslint-disable-next-line @typescript-eslint/no-var-requires
const path = require('path'); const path = require('path');
const files = filePaths.map(filePath => path.resolve(filePath)); const files = filePaths.map(filePath => path.resolve(filePath));
const {objectId} = this._remoteObject; const {objectId} = this._remoteObject;
@ -341,38 +303,27 @@ class ElementHandle extends JSHandle {
} }
} }
async tap() { async tap(): Promise<void> {
await this._scrollIntoViewIfNeeded(); await this._scrollIntoViewIfNeeded();
const {x, y} = await this._clickablePoint(); const {x, y} = await this._clickablePoint();
await this._page.touchscreen.tap(x, y); await this._page.touchscreen.tap(x, y);
} }
async focus() { async focus(): Promise<void> {
await this.evaluate(element => element.focus()); await this.evaluate(element => element.focus());
} }
/** async type(text: string, options?: {delay: number}): Promise<void> {
* @param {string} text
* @param {{delay: (number|undefined)}=} options
*/
async type(text, options) {
await this.focus(); await this.focus();
await this._page.keyboard.type(text, options); await this._page.keyboard.type(text, options);
} }
/** async press(key: string, options?: {delay?: number; text?: string}): Promise<void> {
* @param {string} key
* @param {!{delay?: number, text?: string}=} options
*/
async press(key, options) {
await this.focus(); await this.focus();
await this._page.keyboard.press(key, options); await this._page.keyboard.press(key, options);
} }
/** async boundingBox(): Promise<{x: number; y: number; width: number; height: number}> {
* @return {!Promise<?{x: number, y: number, width: number, height: number}>}
*/
async boundingBox() {
const result = await this._getBoxModel(); const result = await this._getBoxModel();
if (!result) if (!result)
@ -390,7 +341,7 @@ class ElementHandle extends JSHandle {
/** /**
* @return {!Promise<?BoxModel>} * @return {!Promise<?BoxModel>}
*/ */
async boxModel() { async boxModel(): Promise<BoxModel | null> {
const result = await this._getBoxModel(); const result = await this._getBoxModel();
if (!result) if (!result)
@ -407,12 +358,7 @@ class ElementHandle extends JSHandle {
}; };
} }
/** async screenshot(options = {}): Promise<string|Buffer> {
*
* @param {!Object=} options
* @returns {!Promise<string|!Buffer>}
*/
async screenshot(options = {}) {
let needsViewportReset = false; let needsViewportReset = false;
let boundingBox = await this.boundingBox(); let boundingBox = await this.boundingBox();
@ -453,11 +399,7 @@ class ElementHandle extends JSHandle {
return imageData; return imageData;
} }
/** async $(selector: string): Promise<ElementHandle | null> {
* @param {string} selector
* @return {!Promise<?ElementHandle>}
*/
async $(selector) {
const handle = await this.evaluateHandle( const handle = await this.evaluateHandle(
(element, selector) => element.querySelector(selector), (element, selector) => element.querySelector(selector),
selector selector
@ -473,7 +415,7 @@ class ElementHandle extends JSHandle {
* @param {string} selector * @param {string} selector
* @return {!Promise<!Array<!ElementHandle>>} * @return {!Promise<!Array<!ElementHandle>>}
*/ */
async $$(selector) { async $$(selector: string): Promise<ElementHandle[]> {
const arrayHandle = await this.evaluateHandle( const arrayHandle = await this.evaluateHandle(
(element, selector) => element.querySelectorAll(selector), (element, selector) => element.querySelectorAll(selector),
selector selector
@ -489,13 +431,7 @@ class ElementHandle extends JSHandle {
return result; return result;
} }
/** async $eval<T extends any>(selector: string, pageFunction: Function|string, ...args: any[]): Promise<T | undefined> {
* @param {string} selector
* @param {Function|String} pageFunction
* @param {!Array<*>} args
* @return {!Promise<(!Object|undefined)>}
*/
async $eval(selector, pageFunction, ...args) {
const elementHandle = await this.$(selector); const elementHandle = await this.$(selector);
if (!elementHandle) if (!elementHandle)
throw new Error(`Error: failed to find element matching selector "${selector}"`); throw new Error(`Error: failed to find element matching selector "${selector}"`);
@ -504,13 +440,10 @@ class ElementHandle extends JSHandle {
return result; return result;
} }
/** // TODO(jacktfranklin@): consider the types here
* @param {string} selector // we might want $$eval<SelectorType> which returns Promise<SelectorType[]>?
* @param {Function|String} pageFunction // Once ExecutionContext.evaluate is properly typed we can improve this a bunch
* @param {!Array<*>} args async $$eval<T>(selector: string, pageFunction: Function | string, ...args: any[]): Promise<T | undefined> {
* @return {!Promise<(!Object|undefined)>}
*/
async $$eval(selector, pageFunction, ...args) {
const arrayHandle = await this.evaluateHandle( const arrayHandle = await this.evaluateHandle(
(element, selector) => Array.from(element.querySelectorAll(selector)), (element, selector) => Array.from(element.querySelectorAll(selector)),
selector selector
@ -521,11 +454,7 @@ class ElementHandle extends JSHandle {
return result; return result;
} }
/** async $x(expression: string): Promise<ElementHandle[]> {
* @param {string} expression
* @return {!Promise<!Array<!ElementHandle>>}
*/
async $x(expression) {
const arrayHandle = await this.evaluateHandle( const arrayHandle = await this.evaluateHandle(
(element, expression) => { (element, expression) => {
const document = element.ownerDocument || element; const document = element.ownerDocument || element;
@ -549,10 +478,7 @@ class ElementHandle extends JSHandle {
return result; return result;
} }
/** isIntersectingViewport(): Promise<boolean> {
* @returns {!Promise<boolean>}
*/
isIntersectingViewport() {
return this.evaluate(async element => { return this.evaluate(async element => {
const visibleRatio = await new Promise(resolve => { const visibleRatio = await new Promise(resolve => {
const observer = new IntersectionObserver(entries => { const observer = new IntersectionObserver(entries => {
@ -566,7 +492,7 @@ class ElementHandle extends JSHandle {
} }
} }
function computeQuadArea(quad) { function computeQuadArea(quad: Array<{x: number; y: number}>): number {
// Compute sum of all directed areas of adjacent triangles // Compute sum of all directed areas of adjacent triangles
// https://en.wikipedia.org/wiki/Polygon#Simple_polygons // https://en.wikipedia.org/wiki/Polygon#Simple_polygons
let area = 0; let area = 0;
@ -577,15 +503,3 @@ function computeQuadArea(quad) {
} }
return Math.abs(area); return Math.abs(area);
} }
/**
* @typedef {Object} BoxModel
* @property {!Array<!{x: number, y: number}>} content
* @property {!Array<!{x: number, y: number}>} padding
* @property {!Array<!{x: number, y: number}>} border
* @property {!Array<!{x: number, y: number}>} margin
* @property {number} width
* @property {number} height
*/
module.exports = {createJSHandle, JSHandle, ElementHandle};

View File

@ -29,7 +29,9 @@ const Tracing = require('./Tracing');
const {helper, debugError, assert} = require('./helper'); const {helper, debugError, assert} = require('./helper');
const {Coverage} = require('./Coverage'); const {Coverage} = require('./Coverage');
const {Worker: PuppeteerWorker} = require('./Worker'); const {Worker: PuppeteerWorker} = require('./Worker');
const {createJSHandle} = require('./JSHandle'); // Import used as typedef
// eslint-disable-next-line no-unused-vars
const {createJSHandle, JSHandle, ElementHandle} = require('./JSHandle');
const {Accessibility} = require('./Accessibility'); const {Accessibility} = require('./Accessibility');
const {TimeoutSettings} = require('./TimeoutSettings'); const {TimeoutSettings} = require('./TimeoutSettings');
@ -316,7 +318,7 @@ class Page extends EventEmitter {
/** /**
* @param {string} selector * @param {string} selector
* @return {!Promise<?Puppeteer.ElementHandle>} * @return {!Promise<?ElementHandle>}
*/ */
async $(selector) { async $(selector) {
return this.mainFrame().$(selector); return this.mainFrame().$(selector);
@ -325,7 +327,7 @@ class Page extends EventEmitter {
/** /**
* @param {Function|string} pageFunction * @param {Function|string} pageFunction
* @param {!Array<*>} args * @param {!Array<*>} args
* @return {!Promise<!Puppeteer.JSHandle>} * @return {!Promise<!JSHandle>}
*/ */
async evaluateHandle(pageFunction, ...args) { async evaluateHandle(pageFunction, ...args) {
const context = await this.mainFrame().executionContext(); const context = await this.mainFrame().executionContext();
@ -333,8 +335,8 @@ class Page extends EventEmitter {
} }
/** /**
* @param {!Puppeteer.JSHandle} prototypeHandle * @param {!JSHandle} prototypeHandle
* @return {!Promise<!Puppeteer.JSHandle>} * @return {!Promise<!JSHandle>}
*/ */
async queryObjects(prototypeHandle) { async queryObjects(prototypeHandle) {
const context = await this.mainFrame().executionContext(); const context = await this.mainFrame().executionContext();
@ -363,7 +365,7 @@ class Page extends EventEmitter {
/** /**
* @param {string} selector * @param {string} selector
* @return {!Promise<!Array<!Puppeteer.ElementHandle>>} * @return {!Promise<!Array<!ElementHandle>>}
*/ */
async $$(selector) { async $$(selector) {
return this.mainFrame().$$(selector); return this.mainFrame().$$(selector);
@ -371,7 +373,7 @@ class Page extends EventEmitter {
/** /**
* @param {string} expression * @param {string} expression
* @return {!Promise<!Array<!Puppeteer.ElementHandle>>} * @return {!Promise<!Array<!ElementHandle>>}
*/ */
async $x(expression) { async $x(expression) {
return this.mainFrame().$x(expression); return this.mainFrame().$x(expression);
@ -429,7 +431,7 @@ class Page extends EventEmitter {
/** /**
* @param {!{url?: string, path?: string, content?: string, type?: string}} options * @param {!{url?: string, path?: string, content?: string, type?: string}} options
* @return {!Promise<!Puppeteer.ElementHandle>} * @return {!Promise<!ElementHandle>}
*/ */
async addScriptTag(options) { async addScriptTag(options) {
return this.mainFrame().addScriptTag(options); return this.mainFrame().addScriptTag(options);
@ -437,7 +439,7 @@ class Page extends EventEmitter {
/** /**
* @param {!{url?: string, path?: string, content?: string}} options * @param {!{url?: string, path?: string, content?: string}} options
* @return {!Promise<!Puppeteer.ElementHandle>} * @return {!Promise<!ElementHandle>}
*/ */
async addStyleTag(options) { async addStyleTag(options) {
return this.mainFrame().addStyleTag(options); return this.mainFrame().addStyleTag(options);
@ -617,7 +619,7 @@ class Page extends EventEmitter {
/** /**
* @param {string} type * @param {string} type
* @param {!Array<!Puppeteer.JSHandle>} args * @param {!Array<!JSHandle>} args
* @param {Protocol.Runtime.StackTrace=} stackTrace * @param {Protocol.Runtime.StackTrace=} stackTrace
*/ */
_addConsoleMessage(type, args, stackTrace) { _addConsoleMessage(type, args, stackTrace) {
@ -1126,7 +1128,7 @@ class Page extends EventEmitter {
* @param {(string|number|Function)} selectorOrFunctionOrTimeout * @param {(string|number|Function)} selectorOrFunctionOrTimeout
* @param {!{visible?: boolean, hidden?: boolean, timeout?: number, polling?: string|number}=} options * @param {!{visible?: boolean, hidden?: boolean, timeout?: number, polling?: string|number}=} options
* @param {!Array<*>} args * @param {!Array<*>} args
* @return {!Promise<!Puppeteer.JSHandle>} * @return {!Promise<!JSHandle>}
*/ */
waitFor(selectorOrFunctionOrTimeout, options = {}, ...args) { waitFor(selectorOrFunctionOrTimeout, options = {}, ...args) {
return this.mainFrame().waitFor(selectorOrFunctionOrTimeout, options, ...args); return this.mainFrame().waitFor(selectorOrFunctionOrTimeout, options, ...args);
@ -1135,7 +1137,7 @@ class Page extends EventEmitter {
/** /**
* @param {string} selector * @param {string} selector
* @param {!{visible?: boolean, hidden?: boolean, timeout?: number}=} options * @param {!{visible?: boolean, hidden?: boolean, timeout?: number}=} options
* @return {!Promise<?Puppeteer.ElementHandle>} * @return {!Promise<?ElementHandle>}
*/ */
waitForSelector(selector, options = {}) { waitForSelector(selector, options = {}) {
return this.mainFrame().waitForSelector(selector, options); return this.mainFrame().waitForSelector(selector, options);
@ -1144,7 +1146,7 @@ class Page extends EventEmitter {
/** /**
* @param {string} xpath * @param {string} xpath
* @param {!{visible?: boolean, hidden?: boolean, timeout?: number}=} options * @param {!{visible?: boolean, hidden?: boolean, timeout?: number}=} options
* @return {!Promise<?Puppeteer.ElementHandle>} * @return {!Promise<?ElementHandle>}
*/ */
waitForXPath(xpath, options = {}) { waitForXPath(xpath, options = {}) {
return this.mainFrame().waitForXPath(xpath, options); return this.mainFrame().waitForXPath(xpath, options);
@ -1154,7 +1156,7 @@ class Page extends EventEmitter {
* @param {Function} pageFunction * @param {Function} pageFunction
* @param {!{polling?: string|number, timeout?: number}=} options * @param {!{polling?: string|number, timeout?: number}=} options
* @param {!Array<*>} args * @param {!Array<*>} args
* @return {!Promise<!Puppeteer.JSHandle>} * @return {!Promise<!JSHandle>}
*/ */
waitForFunction(pageFunction, options = {}, ...args) { waitForFunction(pageFunction, options = {}, ...args) {
return this.mainFrame().waitForFunction(pageFunction, options, ...args); return this.mainFrame().waitForFunction(pageFunction, options, ...args);
@ -1322,7 +1324,7 @@ class ConsoleMessage {
/** /**
* @param {string} type * @param {string} type
* @param {string} text * @param {string} text
* @param {!Array<!Puppeteer.JSHandle>} args * @param {!Array<!JSHandle>} args
* @param {ConsoleMessage.Location} location * @param {ConsoleMessage.Location} location
*/ */
constructor(type, text, args, location = {}) { constructor(type, text, args, location = {}) {
@ -1347,7 +1349,7 @@ class ConsoleMessage {
} }
/** /**
* @return {!Array<!Puppeteer.JSHandle>} * @return {!Array<!JSHandle>}
*/ */
args() { args() {
return this._args; return this._args;
@ -1364,7 +1366,7 @@ class ConsoleMessage {
class FileChooser { class FileChooser {
/** /**
* @param {CDPSession} client * @param {CDPSession} client
* @param {Puppeteer.ElementHandle} element * @param {ElementHandle} element
* @param {!Protocol.Page.fileChooserOpenedPayload} event * @param {!Protocol.Page.fileChooserOpenedPayload} event
*/ */
constructor(client, element, event) { constructor(client, element, event) {

3
src/externs.d.ts vendored
View File

@ -3,7 +3,6 @@ import {Target as RealTarget} from './Target.js';
import {Page as RealPage} from './Page.js'; import {Page as RealPage} from './Page.js';
import {Mouse as RealMouse, Keyboard as RealKeyboard, Touchscreen as RealTouchscreen} from './Input.js'; import {Mouse as RealMouse, Keyboard as RealKeyboard, Touchscreen as RealTouchscreen} from './Input.js';
import {Frame as RealFrame, FrameManager as RealFrameManager} from './FrameManager.js'; import {Frame as RealFrame, FrameManager as RealFrameManager} from './FrameManager.js';
import {JSHandle as RealJSHandle, ElementHandle as RealElementHandle} from './JSHandle.js';
import {DOMWorld as RealDOMWorld} from './DOMWorld.js'; import {DOMWorld as RealDOMWorld} from './DOMWorld.js';
import {ExecutionContext as RealExecutionContext} from './ExecutionContext.js'; import {ExecutionContext as RealExecutionContext} from './ExecutionContext.js';
import { NetworkManager as RealNetworkManager, Request as RealRequest, Response as RealResponse } from './NetworkManager.js'; import { NetworkManager as RealNetworkManager, Request as RealRequest, Response as RealResponse } from './NetworkManager.js';
@ -19,8 +18,6 @@ declare global {
export class Frame extends RealFrame {} export class Frame extends RealFrame {}
export class FrameManager extends RealFrameManager {} export class FrameManager extends RealFrameManager {}
export class NetworkManager extends RealNetworkManager {} export class NetworkManager extends RealNetworkManager {}
export class ElementHandle extends RealElementHandle {}
export class JSHandle extends RealJSHandle {}
export class DOMWorld extends RealDOMWorld {} export class DOMWorld extends RealDOMWorld {}
export class ExecutionContext extends RealExecutionContext {} export class ExecutionContext extends RealExecutionContext {}
export class Page extends RealPage { } export class Page extends RealPage { }

View File

@ -152,7 +152,7 @@ function checkSources(sources) {
*/ */
function serializeType(type, circular = []) { function serializeType(type, circular = []) {
let typeName = checker.typeToString(type); let typeName = checker.typeToString(type);
if (typeName === 'any' || typeName === '{ [x: string]: string; }') if (typeName === 'any' || typeName === '{ [x: string]: string; }' || typeName === '{}')
typeName = 'Object'; typeName = 'Object';
const nextCircular = [typeName].concat(circular); const nextCircular = [typeName].concat(circular);