From de4f08dc5291c7fc10de19332ee9ef70b8b3b7ce Mon Sep 17 00:00:00 2001 From: Jack Franklin Date: Tue, 5 May 2020 13:53:22 +0100 Subject: [PATCH] chore: migrate src/Page.js to TypeScript (#5809) * chore: migrate src/Page.js to TypeScript The final one! This is a huge file and needs to be split up and tidied, but for now I've left all the definitions in place and converted types accordingly. There's some additional tidying we can do now every `src` file is TS, but I'll leave that for another PR to avoid this one getting any bigger. Co-authored-by: Mathias Bynens --- src/Browser.ts | 14 +- src/Dialog.ts | 9 +- src/FrameManager.ts | 7 +- src/JSHandle.ts | 26 +- src/NetworkManager.ts | 2 +- src/{Page.js => Page.ts} | 934 ++++++++---------------- src/Target.ts | 4 +- src/externs.d.ts | 7 - utils/doclint/check_public_api/index.js | 40 + 9 files changed, 354 insertions(+), 689 deletions(-) rename src/{Page.js => Page.ts} (59%) diff --git a/src/Browser.ts b/src/Browser.ts index 6b137aef678cc..2df47f6799946 100644 --- a/src/Browser.ts +++ b/src/Browser.ts @@ -20,6 +20,7 @@ import * as EventEmitter from 'events'; import {TaskQueue} from './TaskQueue'; import {Events} from './Events'; import {Connection} from './Connection'; +import {Page} from './Page'; import {ChildProcess} from 'child_process'; type BrowserCloseCallback = () => Promise | void; @@ -137,11 +138,11 @@ export class Browser extends EventEmitter { return this._connection.url(); } - async newPage(): Promise { + async newPage(): Promise { return this._defaultContext.newPage(); } - async _createPageInContext(contextId?: string): Promise { + async _createPageInContext(contextId?: string): Promise { const {targetId} = await this._connection.send('Target.createTarget', {url: 'about:blank', browserContextId: contextId || undefined}); const target = await this._targets.get(targetId); assert(await target._initializedPromise, 'Failed to create target for page'); @@ -188,10 +189,7 @@ export class Browser extends EventEmitter { } } - /** - * @return {!Promise>} - */ - async pages(): Promise { + async pages(): Promise { const contextPages = await Promise.all(this.browserContexts().map(context => context.pages())); // Flatten array. return contextPages.reduce((acc, x) => acc.concat(x), []); @@ -245,7 +243,7 @@ export class BrowserContext extends EventEmitter { return this._browser.waitForTarget(target => target.browserContext() === this && predicate(target), options); } - async pages(): Promise { + async pages(): Promise { const pages = await Promise.all( this.targets() .filter(target => target.type() === 'page') @@ -292,7 +290,7 @@ export class BrowserContext extends EventEmitter { await this._connection.send('Browser.resetPermissions', {browserContextId: this._id || undefined}); } - newPage(): Promise { + newPage(): Promise { return this._browser._createPageInContext(this._id); } diff --git a/src/Dialog.ts b/src/Dialog.ts index 792c37700ceab..214c7711379e4 100644 --- a/src/Dialog.ts +++ b/src/Dialog.ts @@ -17,14 +17,17 @@ import {assert} from './helper'; import {CDPSession} from './Connection'; -enum DialogType { +/* TODO(jacktfranklin): protocol.d.ts defines this + * so let's ditch this and avoid the duplication + */ +export enum DialogType { Alert = 'alert', BeforeUnload = 'beforeunload', Confirm = 'confirm', Prompt = 'prompt' } -class Dialog { +export class Dialog { static Type = DialogType; private _client: CDPSession; @@ -69,5 +72,3 @@ class Dialog { }); } } - -export = {Dialog}; diff --git a/src/FrameManager.ts b/src/FrameManager.ts index fcd25eb7d7bfe..f7b4712a878ea 100644 --- a/src/FrameManager.ts +++ b/src/FrameManager.ts @@ -25,12 +25,13 @@ import {TimeoutSettings} from './TimeoutSettings'; import {CDPSession} from './Connection'; import {JSHandle, ElementHandle} from './JSHandle'; import {MouseButtonInput} from './Input'; +import {Page} from './Page'; const UTILITY_WORLD_NAME = '__puppeteer_utility_world__'; export class FrameManager extends EventEmitter { _client: CDPSession; - _page: Puppeteer.Page; + _page: Page; _networkManager: NetworkManager; _timeoutSettings: TimeoutSettings; _frames = new Map(); @@ -38,7 +39,7 @@ export class FrameManager extends EventEmitter { _isolatedWorlds = new Set(); _mainFrame: Frame; - constructor(client: CDPSession, page: Puppeteer.Page, ignoreHTTPSErrors: boolean, timeoutSettings: TimeoutSettings) { + constructor(client: CDPSession, page: Page, ignoreHTTPSErrors: boolean, timeoutSettings: TimeoutSettings) { super(); this._client = client; this._page = page; @@ -155,7 +156,7 @@ export class FrameManager extends EventEmitter { this._handleFrameTree(child); } - page(): Puppeteer.Page { + page(): Page { return this._page; } diff --git a/src/JSHandle.ts b/src/JSHandle.ts index 61f53db40eccc..40833ea0d4ed3 100644 --- a/src/JSHandle.ts +++ b/src/JSHandle.ts @@ -16,6 +16,7 @@ import {helper, assert, debugError} from './helper'; import {ExecutionContext} from './ExecutionContext'; +import {Page} from './Page'; import {CDPSession} from './Connection'; import {KeyInput} from './USKeyboardLayout'; import {FrameManager, Frame} from './FrameManager'; @@ -124,16 +125,9 @@ export class JSHandle { } export class ElementHandle extends JSHandle { - _page: Puppeteer.Page; + _page: Page; _frameManager: FrameManager; - /** - * @param {!ExecutionContext} context - * @param {!CDPSession} client - * @param {!Protocol.Runtime.RemoteObject} remoteObject - * @param {!Puppeteer.Page} page - * @param {!FrameManager} frameManager - */ - constructor(context: ExecutionContext, client: CDPSession, remoteObject: Protocol.Runtime.RemoteObject, page: Puppeteer.Page, frameManager: FrameManager) { + constructor(context: ExecutionContext, client: CDPSession, remoteObject: Protocol.Runtime.RemoteObject, page: Page, frameManager: FrameManager) { super(context, client, remoteObject); this._client = client; this._remoteObject = remoteObject; @@ -231,12 +225,6 @@ export class ElementHandle extends JSHandle { ]; } - /** - * @param {!Array<{x: number, y: number}>} quad - * @param {number} width - * @param {number} height - * @return {!Array<{x: number, y: number}>} - */ _intersectQuadWithViewport(quad: Array<{x: number; y: number}>, width: number, height: number): Array<{x: number; y: number}> { return quad.map(point => ({ x: Math.min(Math.max(point.x, 0), width), @@ -256,10 +244,6 @@ export class ElementHandle extends JSHandle { await this._page.mouse.click(x, y, options); } - /** - * @param {!Array} values - * @return {!Promise>} - */ async select(...values: string[]): Promise { for (const value of values) assert(helper.isString(value), 'Values must be strings. Found value "' + value + '" of type "' + (typeof value) + '"'); @@ -439,10 +423,6 @@ export class ElementHandle extends JSHandle { return null; } - /** - * @param {string} selector - * @return {!Promise>} - */ async $$(selector: string): Promise { const defaultHandler = (element: Element, selector: string) => element.querySelectorAll(selector); const {updatedSelector, queryHandler} = getQueryHandlerAndSelector(selector, defaultHandler); diff --git a/src/NetworkManager.ts b/src/NetworkManager.ts index d1b998a97b830..4f40196974265 100644 --- a/src/NetworkManager.ts +++ b/src/NetworkManager.ts @@ -19,7 +19,7 @@ import {Events} from './Events'; import {CDPSession} from './Connection'; import {FrameManager, Frame} from './FrameManager'; -interface Credentials { +export interface Credentials { username: string; password: string; } diff --git a/src/Page.js b/src/Page.ts similarity index 59% rename from src/Page.js rename to src/Page.ts index 2b69916cb63da..96e0d8f7d1376 100644 --- a/src/Page.js +++ b/src/Page.ts @@ -14,54 +14,117 @@ * limitations under the License. */ -const fs = require('fs'); -const EventEmitter = require('events'); -const mime = require('mime'); -const {Events} = require('./Events'); -// CDPSession is used only as a typedef -// eslint-disable-next-line no-unused-vars -const {Connection, CDPSession} = require('./Connection'); -const {Dialog} = require('./Dialog'); -const {EmulationManager} = require('./EmulationManager'); -// Import used as typedef -// eslint-disable-next-line no-unused-vars -const {Frame, FrameManager} = require('./FrameManager'); -const {Keyboard, Mouse, Touchscreen} = require('./Input'); -const {Tracing} = require('./Tracing'); -const {helper, debugError, assert} = require('./helper'); -const {Coverage} = require('./Coverage'); -const {Worker: PuppeteerWorker} = require('./Worker'); -// Import used as typedef -// eslint-disable-next-line no-unused-vars -const {Browser, BrowserContext} = require('./Browser'); -// Import used as typedef -// eslint-disable-next-line no-unused-vars -const {Target} = require('./Target'); -// Import used as typedef -// eslint-disable-next-line no-unused-vars -const {createJSHandle, JSHandle, ElementHandle} = require('./JSHandle'); -// Import used as typedef -// eslint-disable-next-line no-unused-vars -const {Request: PuppeteerRequest, Response: PuppeteerResponse} = require('./NetworkManager'); -const {Accessibility} = require('./Accessibility'); -const {TimeoutSettings} = require('./TimeoutSettings'); - -// This import is used as a TypeDef, but ESLint's rule doesn't -// understand that unfortunately. -// eslint-disable-next-line no-unused-vars -const {TaskQueue} = require('./TaskQueue'); +import * as fs from 'fs'; +import * as EventEmitter from 'events'; +import * as mime from 'mime'; +import {Events} from './Events'; +import {Connection, CDPSession} from './Connection'; +import {Dialog} from './Dialog'; +import {EmulationManager} from './EmulationManager'; +import {Frame, FrameManager} from './FrameManager'; +import {Keyboard, Mouse, Touchscreen, MouseButtonInput} from './Input'; +import {Tracing} from './Tracing'; +import {helper, debugError, assert} from './helper'; +import {Coverage} from './Coverage'; +import {Worker as PuppeteerWorker} from './Worker'; +import {Browser, BrowserContext} from './Browser'; +import {Target} from './Target'; +import {createJSHandle, JSHandle, ElementHandle} from './JSHandle'; +import {Request as PuppeteerRequest, Response as PuppeteerResponse, Credentials} from './NetworkManager'; +import {Accessibility} from './Accessibility'; +import {TimeoutSettings} from './TimeoutSettings'; +import {PuppeteerLifeCycleEvent} from './LifecycleWatcher'; +import {TaskQueue} from './TaskQueue'; + const writeFileAsync = helper.promisify(fs.writeFile); -class Page extends EventEmitter { - /** - * @param {!CDPSession} client - * @param {!Target} target - * @param {boolean} ignoreHTTPSErrors - * @param {?Puppeteer.Viewport} defaultViewport - * @param {!TaskQueue} screenshotTaskQueue - * @return {!Promise} - */ - static async create(client, target, ignoreHTTPSErrors, defaultViewport, screenshotTaskQueue) { +interface Metrics { + Timestamp?: number; + Documents?: number; + Frames?: number; + JSEventListeners?: number; + Nodes?: number; + LayoutCount?: number; + RecalcStyleCount?: number; + LayoutDuration?: number; + RecalcStyleDuration?: number; + ScriptDuration?: number; + TaskDuration?: number; + JSHeapUsedSize?: number; + JSHeapTotalSize?: number; +} + +interface WaitForOptions { + timeout?: number; + waitUntil?: PuppeteerLifeCycleEvent|PuppeteerLifeCycleEvent[]; +} + +interface MediaFeature { + name: string; + value: string; +} + +interface ScreenshotClip { + x: number; + y: number; + width: number; + height: number; +} + +interface ScreenshotOptions { + type?: 'png' | 'jpeg'; + path?: string; + fullPage?: boolean; + clip?: ScreenshotClip; + quality?: number; + omitBackground?: boolean; + encoding?: string; +} + +interface PDFMargin { + top?: string|number; + bottom?: string|number; + left?: string|number; + right?: string|number; +} + +interface PDFOptions { + scale?: number; + displayHeaderFooter?: boolean; + headerTemplate?: string; + footerTemplate?: string; + printBackground?: boolean; + landscape?: boolean; + pageRanges?: string; + format?: string; + width?: string|number; + height?: string|number; + preferCSSPageSize?: boolean; + margin?: PDFMargin; + path?: string; +} + +interface PaperFormat { + width: number; + height: number; +} + +const paperFormats: Record = { + letter: {width: 8.5, height: 11}, + legal: {width: 8.5, height: 14}, + tabloid: {width: 11, height: 17}, + ledger: {width: 17, height: 11}, + a0: {width: 33.1, height: 46.8}, + a1: {width: 23.4, height: 33.1}, + a2: {width: 16.54, height: 23.4}, + a3: {width: 11.7, height: 16.54}, + a4: {width: 8.27, height: 11.7}, + a5: {width: 5.83, height: 8.27}, + a6: {width: 4.13, height: 5.83}, +} as const; + +export class Page extends EventEmitter { + static async create(client: CDPSession, target: Target, ignoreHTTPSErrors: boolean, defaultViewport: Puppeteer.Viewport | null, screenshotTaskQueue: TaskQueue): Promise { const page = new Page(client, target, ignoreHTTPSErrors, screenshotTaskQueue); await page._initialize(); if (defaultViewport) @@ -69,37 +132,43 @@ class Page extends EventEmitter { return page; } - /** - * @param {!CDPSession} client - * @param {!Target} target - * @param {boolean} ignoreHTTPSErrors - * @param {!TaskQueue} screenshotTaskQueue - */ - constructor(client, target, ignoreHTTPSErrors, screenshotTaskQueue) { + _closed = false; + _client: CDPSession; + _target: Target; + _keyboard: Keyboard; + _mouse: Mouse; + _timeoutSettings = new TimeoutSettings(); + _touchscreen: Touchscreen; + _accessibility: Accessibility; + _frameManager: FrameManager; + _emulationManager: EmulationManager; + _tracing: Tracing; + _pageBindings = new Map(); + _coverage: Coverage; + _javascriptEnabled = true; + _viewport: Puppeteer.Viewport | null; + _screenshotTaskQueue: TaskQueue; + _workers = new Map(); + // TODO: improve this typedef - it's a function that takes a file chooser or something? + _fileChooserInterceptors = new Set(); + + _disconnectPromise?: Promise; + + constructor(client: CDPSession, target: Target, ignoreHTTPSErrors: boolean, screenshotTaskQueue: TaskQueue) { super(); - this._closed = false; this._client = client; this._target = target; this._keyboard = new Keyboard(client); this._mouse = new Mouse(client, this._keyboard); - this._timeoutSettings = new TimeoutSettings(); this._touchscreen = new Touchscreen(client, this._keyboard); this._accessibility = new Accessibility(client); - /** @type {!FrameManager} */ this._frameManager = new FrameManager(client, this, ignoreHTTPSErrors, this._timeoutSettings); this._emulationManager = new EmulationManager(client); this._tracing = new Tracing(client); - /** @type {!Map} */ - this._pageBindings = new Map(); this._coverage = new Coverage(client); - this._javascriptEnabled = true; - /** @type {?Puppeteer.Viewport} */ - this._viewport = null; - this._screenshotTaskQueue = screenshotTaskQueue; + this._viewport = null; - /** @type {!Map} */ - this._workers = new Map(); client.on('Target.attachedToTarget', event => { if (event.targetInfo.type !== 'worker') { // If we don't detach from service workers, they will never die. @@ -132,13 +201,13 @@ class Page extends EventEmitter { networkManager.on(Events.NetworkManager.RequestFinished, event => this.emit(Events.Page.RequestFinished, event)); this._fileChooserInterceptors = new Set(); - client.on('Page.domContentEventFired', event => this.emit(Events.Page.DOMContentLoaded)); - client.on('Page.loadEventFired', event => this.emit(Events.Page.Load)); + client.on('Page.domContentEventFired', () => this.emit(Events.Page.DOMContentLoaded)); + client.on('Page.loadEventFired', () => this.emit(Events.Page.Load)); client.on('Runtime.consoleAPICalled', event => this._onConsoleAPI(event)); client.on('Runtime.bindingCalled', event => this._onBindingCalled(event)); client.on('Page.javascriptDialogOpening', event => this._onDialog(event)); client.on('Runtime.exceptionThrown', exception => this._handleException(exception.exceptionDetails)); - client.on('Inspector.targetCrashed', event => this._onTargetCrashed()); + client.on('Inspector.targetCrashed', () => this._onTargetCrashed()); client.on('Performance.metrics', event => this._emitMetrics(event)); client.on('Log.entryAdded', event => this._onLogEntryAdded(event)); client.on('Page.fileChooserOpened', event => this._onFileChooser(event)); @@ -148,7 +217,7 @@ class Page extends EventEmitter { }); } - async _initialize() { + async _initialize(): Promise { await Promise.all([ this._frameManager.initialize(), this._client.send('Target.setAutoAttach', {autoAttach: true, waitForDebuggerOnStart: false, flatten: true}), @@ -157,10 +226,7 @@ class Page extends EventEmitter { ]); } - /** - * @param {!Protocol.Page.fileChooserOpenedPayload} event - */ - async _onFileChooser(event) { + async _onFileChooser(event: Protocol.Page.fileChooserOpenedPayload): Promise { if (!this._fileChooserInterceptors.size) return; const frame = this._frameManager.frame(event.frameId); @@ -173,11 +239,7 @@ class Page extends EventEmitter { interceptor.call(null, fileChooser); } - /** - * @param {!{timeout?: number}=} options - * @return !Promise} - */ - async waitForFileChooser(options = {}) { + async waitForFileChooser(options: {timeout?: number} = {}): Promise { if (!this._fileChooserInterceptors.size) await this._client.send('Page.setInterceptFileChooserDialog', {enabled: true}); @@ -185,18 +247,15 @@ class Page extends EventEmitter { timeout = this._timeoutSettings.timeout(), } = options; let callback; - const promise = new Promise(x => callback = x); + const promise = new Promise(x => callback = x); this._fileChooserInterceptors.add(callback); - return helper.waitWithTimeout(promise, 'waiting for file chooser', timeout).catch(error => { + return helper.waitWithTimeout(promise, 'waiting for file chooser', timeout).catch(error => { this._fileChooserInterceptors.delete(callback); throw error; }); } - /** - * @param {!{longitude: number, latitude: number, accuracy: (number|undefined)}} options - */ - async setGeolocation(options) { + async setGeolocation(options: {longitude: number; latitude: number; accuracy?: number}): Promise { const {longitude, latitude, accuracy = 0} = options; if (longitude < -180 || longitude > 180) throw new Error(`Invalid longitude "${longitude}": precondition -180 <= LONGITUDE <= 180 failed.`); @@ -207,35 +266,23 @@ class Page extends EventEmitter { await this._client.send('Emulation.setGeolocationOverride', {longitude, latitude, accuracy}); } - /** - * @return {!Target} - */ - target() { + target(): Target { return this._target; } - /** - * @return {!Browser} - */ - browser() { + browser(): Browser { return this._target.browser(); } - /** - * @return {!BrowserContext} - */ - browserContext() { + browserContext(): BrowserContext { return this._target.browserContext(); } - _onTargetCrashed() { + _onTargetCrashed(): void { this.emit('error', new Error('Page crashed!')); } - /** - * @param {!Protocol.Log.entryAddedPayload} event - */ - _onLogEntryAdded(event) { + _onLogEntryAdded(event: Protocol.Log.entryAddedPayload): void { const {level, text, args, source, url, lineNumber} = event.entry; if (args) args.map(arg => helper.releaseObject(this._client, arg)); @@ -243,164 +290,91 @@ class Page extends EventEmitter { this.emit(Events.Page.Console, new ConsoleMessage(level, text, [], {url, lineNumber})); } - /** - * @return {!Frame} - */ - mainFrame() { + mainFrame(): Frame { return this._frameManager.mainFrame(); } - /** - * @return {!Keyboard} - */ - get keyboard() { + get keyboard(): Keyboard { return this._keyboard; } - /** - * @return {!Touchscreen} - */ - get touchscreen() { + get touchscreen(): Touchscreen { return this._touchscreen; } - /** - * @return {!Coverage} - */ - get coverage() { + get coverage(): Coverage { return this._coverage; } - /** - * @return {!Tracing} - */ - get tracing() { + get tracing(): Tracing { return this._tracing; } - /** - * @return {!Accessibility} - */ - get accessibility() { + get accessibility(): Accessibility { return this._accessibility; } - /** - * @return {!Array} - */ - frames() { + frames(): Frame[] { return this._frameManager.frames(); } - /** - * @return {!Array} - */ - workers() { + workers(): PuppeteerWorker[] { return Array.from(this._workers.values()); } - /** - * @param {boolean} value - */ - async setRequestInterception(value) { + async setRequestInterception(value: boolean): Promise { return this._frameManager.networkManager().setRequestInterception(value); } - /** - * @param {boolean} enabled - */ - setOfflineMode(enabled) { + setOfflineMode(enabled: boolean): Promise { return this._frameManager.networkManager().setOfflineMode(enabled); } - /** - * @param {number} timeout - */ - setDefaultNavigationTimeout(timeout) { + setDefaultNavigationTimeout(timeout: number): void { this._timeoutSettings.setDefaultNavigationTimeout(timeout); } - /** - * @param {number} timeout - */ - setDefaultTimeout(timeout) { + setDefaultTimeout(timeout: number): void { this._timeoutSettings.setDefaultTimeout(timeout); } - /** - * @param {string} selector - * @return {!Promise} - */ - async $(selector) { + async $(selector: string): Promise { return this.mainFrame().$(selector); } - /** - * @param {Function|string} pageFunction - * @param {!Array<*>} args - * @return {!Promise} - */ - async evaluateHandle(pageFunction, ...args) { + async evaluateHandle(pageFunction: Function | string, ...args: unknown[]): Promise { const context = await this.mainFrame().executionContext(); return context.evaluateHandle(pageFunction, ...args); } - /** - * @param {!JSHandle} prototypeHandle - * @return {!Promise} - */ - async queryObjects(prototypeHandle) { + async queryObjects(prototypeHandle: JSHandle): Promise { const context = await this.mainFrame().executionContext(); return context.queryObjects(prototypeHandle); } - /** - * @param {string} selector - * @param {Function|string} pageFunction - * @param {!Array<*>} args - * @return {!Promise<(!Object|undefined)>} - */ - async $eval(selector, pageFunction, ...args) { - return this.mainFrame().$eval(selector, pageFunction, ...args); + async $eval(selector: string, pageFunction: Function | string, ...args: unknown[]): Promise { + return this.mainFrame().$eval(selector, pageFunction, ...args); } - /** - * @param {string} selector - * @param {Function|string} pageFunction - * @param {!Array<*>} args - * @return {!Promise<(!Object|undefined)>} - */ - async $$eval(selector, pageFunction, ...args) { - return this.mainFrame().$$eval(selector, pageFunction, ...args); + async $$eval(selector: string, pageFunction: Function | string, ...args: unknown[]): Promise { + return this.mainFrame().$$eval(selector, pageFunction, ...args); } - /** - * @param {string} selector - * @return {!Promise>} - */ - async $$(selector) { + async $$(selector: string): Promise { return this.mainFrame().$$(selector); } - /** - * @param {string} expression - * @return {!Promise>} - */ - async $x(expression) { + async $x(expression: string): Promise { return this.mainFrame().$x(expression); } - /** - * @param {!Array} urls - * @return {!Promise>} - */ - async cookies(...urls) { + async cookies(...urls: string[]): Promise { const originalCookies = (await this._client.send('Network.getCookies', { urls: urls.length ? urls : [this.url()] })).cookies; const unsupportedCookieAttributes = ['priority']; - const filterUnsupportedAttributes = cookie => { + const filterUnsupportedAttributes = (cookie: Protocol.Network.Cookie): Protocol.Network.Cookie => { for (const attr of unsupportedCookieAttributes) delete cookie[attr]; return cookie; @@ -408,10 +382,7 @@ class Page extends EventEmitter { return originalCookies.map(filterUnsupportedAttributes); } - /** - * @param {Array} cookies - */ - async deleteCookie(...cookies) { + async deleteCookie(...cookies: Protocol.Network.deleteCookiesParameters[]): Promise { const pageURL = this.url(); for (const cookie of cookies) { const item = Object.assign({}, cookie); @@ -421,10 +392,7 @@ class Page extends EventEmitter { } } - /** - * @param {Array} cookies - */ - async setCookie(...cookies) { + async setCookie(...cookies: Protocol.Network.CookieParam[]): Promise { const pageURL = this.url(); const startsWithHTTP = pageURL.startsWith('http'); const items = cookies.map(cookie => { @@ -440,27 +408,15 @@ class Page extends EventEmitter { await this._client.send('Network.setCookies', {cookies: items}); } - /** - * @param {!{url?: string, path?: string, content?: string, type?: string}} options - * @return {!Promise} - */ - async addScriptTag(options) { + async addScriptTag(options: {url?: string; path?: string; content?: string; type?: string}): Promise { return this.mainFrame().addScriptTag(options); } - /** - * @param {!{url?: string, path?: string, content?: string}} options - * @return {!Promise} - */ - async addStyleTag(options) { + async addStyleTag(options: {url?: string; path?: string; content?: string}): Promise { return this.mainFrame().addStyleTag(options); } - /** - * @param {string} name - * @param {Function} puppeteerFunction - */ - async exposeFunction(name, puppeteerFunction) { + async exposeFunction(name: string, puppeteerFunction: Function): Promise { if (this._pageBindings.has(name)) throw new Error(`Failed to add page binding with name ${name}: window['${name}'] already exists!`); this._pageBindings.set(name, puppeteerFunction); @@ -470,11 +426,14 @@ class Page extends EventEmitter { await this._client.send('Page.addScriptToEvaluateOnNewDocument', {source: expression}); await Promise.all(this.frames().map(frame => frame.evaluate(expression).catch(debugError))); - function addPageBinding(bindingName) { - const win = /** @type * */ (window); - const binding = /** @type function(string):* */ (win[bindingName]); + function addPageBinding(bindingName): void { + /* Cast window to any here as we're about to add properties to it + * via win[bindingName] which TypeScript doesn't like. + */ + const win = window as any; + const binding = win[bindingName]; - win[bindingName] = (...args) => { + win[bindingName] = (...args: unknown[]): Promise => { const me = window[bindingName]; let callbacks = me['callbacks']; if (!callbacks) { @@ -490,50 +449,31 @@ class Page extends EventEmitter { } } - /** - * @param {?{username: string, password: string}} credentials - */ - async authenticate(credentials) { + async authenticate(credentials: Credentials): Promise { return this._frameManager.networkManager().authenticate(credentials); } - /** - * @param {!Object} headers - */ - async setExtraHTTPHeaders(headers) { + async setExtraHTTPHeaders(headers: Record): Promise { return this._frameManager.networkManager().setExtraHTTPHeaders(headers); } - /** - * @param {string} userAgent - */ - async setUserAgent(userAgent) { + async setUserAgent(userAgent: string): Promise { return this._frameManager.networkManager().setUserAgent(userAgent); } - /** - * @return {!Promise} - */ - async metrics() { + async metrics(): Promise { const response = await this._client.send('Performance.getMetrics'); return this._buildMetricsObject(response.metrics); } - /** - * @param {!Protocol.Performance.metricsPayload} event - */ - _emitMetrics(event) { + _emitMetrics(event: Protocol.Performance.metricsPayload): void { this.emit(Events.Page.Metrics, { title: event.title, metrics: this._buildMetricsObject(event.metrics) }); } - /** - * @param {?Array} metrics - * @return {!Metrics} - */ - _buildMetricsObject(metrics) { + _buildMetricsObject(metrics?: Protocol.Performance.Metric[]): Metrics { const result = {}; for (const metric of metrics || []) { if (supportedMetrics.has(metric.name)) @@ -542,20 +482,14 @@ class Page extends EventEmitter { return result; } - /** - * @param {!Protocol.Runtime.ExceptionDetails} exceptionDetails - */ - _handleException(exceptionDetails) { + _handleException(exceptionDetails: Protocol.Runtime.ExceptionDetails): void { const message = helper.getExceptionMessage(exceptionDetails); const err = new Error(message); err.stack = ''; // Don't report clientside error with a node stack attached this.emit(Events.Page.PageError, err); } - /** - * @param {!Protocol.Runtime.consoleAPICalledPayload} event - */ - async _onConsoleAPI(event) { + async _onConsoleAPI(event: Protocol.Runtime.consoleAPICalledPayload): Promise { if (event.executionContextId === 0) { // DevTools protocol stores the last 1000 console messages. These // messages are always reported even for removed execution contexts. In @@ -577,10 +511,7 @@ class Page extends EventEmitter { this._addConsoleMessage(event.type, values, event.stackTrace); } - /** - * @param {!Protocol.Runtime.bindingCalledPayload} event - */ - async _onBindingCalled(event) { + async _onBindingCalled(event: Protocol.Runtime.bindingCalledPayload): Promise { const {name, seq, args} = JSON.parse(event.payload); let expression = null; try { @@ -594,46 +525,25 @@ class Page extends EventEmitter { } this._client.send('Runtime.evaluate', {expression, contextId: event.executionContextId}).catch(debugError); - /** - * @param {string} name - * @param {number} seq - * @param {*} result - */ - function deliverResult(name, seq, result) { + function deliverResult(name: string, seq: number, result: unknown): void { window[name]['callbacks'].get(seq).resolve(result); window[name]['callbacks'].delete(seq); } - /** - * @param {string} name - * @param {number} seq - * @param {string} message - * @param {string} stack - */ - function deliverError(name, seq, message, stack) { + function deliverError(name: string, seq: number, message: string, stack: string): void { const error = new Error(message); error.stack = stack; window[name]['callbacks'].get(seq).reject(error); window[name]['callbacks'].delete(seq); } - /** - * @param {string} name - * @param {number} seq - * @param {*} value - */ - function deliverErrorValue(name, seq, value) { + function deliverErrorValue(name: string, seq: number, value: unknown): void { window[name]['callbacks'].get(seq).reject(value); window[name]['callbacks'].delete(seq); } } - /** - * @param {string} type - * @param {!Array} args - * @param {Protocol.Runtime.StackTrace=} stackTrace - */ - _addConsoleMessage(type, args, stackTrace) { + _addConsoleMessage(type: string, args: JSHandle[], stackTrace?: Protocol.Runtime.StackTrace): void { if (!this.listenerCount(Events.Page.Console)) { args.forEach(arg => arg.dispose()); return; @@ -655,7 +565,7 @@ class Page extends EventEmitter { this.emit(Events.Page.Console, message); } - _onDialog(event) { + _onDialog(event: Protocol.Page.javascriptDialogOpeningPayload): void { let dialogType = null; if (event.type === 'alert') dialogType = Dialog.Type.Alert; @@ -666,75 +576,47 @@ class Page extends EventEmitter { else if (event.type === 'beforeunload') dialogType = Dialog.Type.BeforeUnload; assert(dialogType, 'Unknown javascript dialog type: ' + event.type); + const dialog = new Dialog(this._client, dialogType, event.message, event.defaultPrompt); this.emit(Events.Page.Dialog, dialog); } - /** - * @return {!string} - */ - url() { + url(): string { return this.mainFrame().url(); } - /** - * @return {!Promise} - */ - async content() { + async content(): Promise { return await this._frameManager.mainFrame().content(); } - /** - * @param {string} html - * @param {!{timeout?: number, waitUntil?: !Puppeteer.PuppeteerLifeCycleEvent|!Array}=} options - */ - async setContent(html, options) { + async setContent(html: string, options: WaitForOptions): Promise { await this._frameManager.mainFrame().setContent(html, options); } - /** - * @param {string} url - * @param {!{referer?: string, timeout?: number, waitUntil?: !Puppeteer.PuppeteerLifeCycleEvent|!Array}=} options - * @return {!Promise} - */ - async goto(url, options) { + async goto(url: string, options: WaitForOptions & { referer?: string }): Promise { return await this._frameManager.mainFrame().goto(url, options); } - /** - * @param {!{timeout?: number, waitUntil?: !Puppeteer.PuppeteerLifeCycleEvent|!Array}=} options - * @return {!Promise} - */ - async reload(options) { - const result = await Promise.all([ + async reload(options?: WaitForOptions): Promise { + const result = await Promise.all([ this.waitForNavigation(options), this._client.send('Page.reload') ]); - const response = /** @type PuppeteerResponse */ (result[0]); - return response; + return result[0]; } - /** - * @param {!{timeout?: number, waitUntil?: !Puppeteer.PuppeteerLifeCycleEvent|!Array}=} options - * @return {!Promise} - */ - async waitForNavigation(options = {}) { + async waitForNavigation(options: WaitForOptions = {}): Promise { return await this._frameManager.mainFrame().waitForNavigation(options); } - _sessionClosePromise() { + _sessionClosePromise(): Promise { if (!this._disconnectPromise) this._disconnectPromise = new Promise(fulfill => this._client.once(Events.CDPSession.Disconnected, () => fulfill(new Error('Target closed')))); return this._disconnectPromise; } - /** - * @param {(string|Function)} urlOrPredicate - * @param {!{timeout?: number}=} options - * @return {!Promise} - */ - async waitForRequest(urlOrPredicate, options = {}) { + async waitForRequest(urlOrPredicate: string | Function, options: {timeout?: number} = {}): Promise { const { timeout = this._timeoutSettings.timeout(), } = options; @@ -747,12 +629,7 @@ class Page extends EventEmitter { }, timeout, this._sessionClosePromise()); } - /** - * @param {(string|Function)} urlOrPredicate - * @param {!{timeout?: number}=} options - * @return {!Promise} - */ - async waitForResponse(urlOrPredicate, options = {}) { + async waitForResponse(urlOrPredicate: string | Function, options: {timeout?: number} = {}): Promise { const { timeout = this._timeoutSettings.timeout(), } = options; @@ -765,82 +642,54 @@ class Page extends EventEmitter { }, timeout, this._sessionClosePromise()); } - /** - * @param {!{timeout?: number, waitUntil?: !Puppeteer.PuppeteerLifeCycleEvent|!Array}=} options - * @return {!Promise} - */ - async goBack(options) { + async goBack(options: WaitForOptions): Promise { return this._go(-1, options); } - /** - * @param {!{timeout?: number, waitUntil?: !Puppeteer.PuppeteerLifeCycleEvent|!Array}=} options - * @return {!Promise} - */ - async goForward(options) { + async goForward(options: WaitForOptions): Promise { return this._go(+1, options); } - /** - * @param {!{timeout?: number, waitUntil?: !Puppeteer.PuppeteerLifeCycleEvent|!Array}=} options - * @return {!Promise} - */ - async _go(delta, options) { + async _go(delta: number, options: WaitForOptions): Promise { const history = await this._client.send('Page.getNavigationHistory'); const entry = history.entries[history.currentIndex + delta]; if (!entry) return null; - const result = await Promise.all([ + const result = await Promise.all([ this.waitForNavigation(options), this._client.send('Page.navigateToHistoryEntry', {entryId: entry.id}), ]); - const response = /** @type PuppeteerResponse */ (result[0]); - return response; + return result[0]; } - async bringToFront() { + async bringToFront(): Promise { await this._client.send('Page.bringToFront'); } - /** - * @param {!{viewport: !Puppeteer.Viewport, userAgent: string}} options - */ - async emulate(options) { + async emulate(options: {viewport: Puppeteer.Viewport; userAgent: string}): Promise { await Promise.all([ this.setViewport(options.viewport), this.setUserAgent(options.userAgent) ]); } - /** - * @param {boolean} enabled - */ - async setJavaScriptEnabled(enabled) { + async setJavaScriptEnabled(enabled: boolean): Promise { if (this._javascriptEnabled === enabled) return; this._javascriptEnabled = enabled; await this._client.send('Emulation.setScriptExecutionDisabled', {value: !enabled}); } - /** - * @param {boolean} enabled - */ - async setBypassCSP(enabled) { + async setBypassCSP(enabled: boolean): Promise { await this._client.send('Page.setBypassCSP', {enabled}); } - /** - * @param {?string} type - */ - async emulateMediaType(type) { + async emulateMediaType(type?: string): Promise { assert(type === 'screen' || type === 'print' || type === null, 'Unsupported media type: ' + type); await this._client.send('Emulation.setEmulatedMedia', {media: type || ''}); } - /** - * @param {?Array} features - */ - async emulateMediaFeatures(features) { + async emulateMediaFeatures(features?: MediaFeature[]): Promise { if (features === null) await this._client.send('Emulation.setEmulatedMedia', {features: null}); if (Array.isArray(features)) { @@ -853,10 +702,7 @@ class Page extends EventEmitter { } } - /** - * @param {?string} timezoneId - */ - async emulateTimezone(timezoneId) { + async emulateTimezone(timezoneId?: string): Promise { try { await this._client.send('Emulation.setTimezoneOverride', {timezoneId: timezoneId || ''}); } catch (error) { @@ -866,53 +712,31 @@ class Page extends EventEmitter { } } - /** - * @param {!Puppeteer.Viewport} viewport - */ - async setViewport(viewport) { + async setViewport(viewport: Puppeteer.Viewport): Promise { const needsReload = await this._emulationManager.emulateViewport(viewport); this._viewport = viewport; if (needsReload) await this.reload(); } - /** - * @return {?Puppeteer.Viewport} - */ - viewport() { + viewport(): Puppeteer.Viewport | null { return this._viewport; } - /** - * @param {Function|string} pageFunction - * @param {!Array<*>} args - * @return {!Promise<*>} - */ - async evaluate(pageFunction, ...args) { - return this._frameManager.mainFrame().evaluate(pageFunction, ...args); + async evaluate(pageFunction: Function | string, ...args: unknown[]): Promise { + return this._frameManager.mainFrame().evaluate(pageFunction, ...args); } - /** - * @param {Function|string} pageFunction - * @param {!Array<*>} args - */ - async evaluateOnNewDocument(pageFunction, ...args) { + async evaluateOnNewDocument(pageFunction: Function | string, ...args: unknown[]): Promise { const source = helper.evaluationString(pageFunction, ...args); await this._client.send('Page.addScriptToEvaluateOnNewDocument', {source}); } - /** - * @param {boolean} enabled - */ - async setCacheEnabled(enabled = true) { + async setCacheEnabled(enabled = true): Promise { await this._frameManager.networkManager().setCacheEnabled(enabled); } - /** - * @param {!ScreenshotOptions=} options - * @return {!Promise} - */ - async screenshot(options = {}) { + async screenshot(options: ScreenshotOptions = {}): Promise { let screenshotType = null; // options.type takes precedence over inferring the type from options.path // because it may be a 0-length file with no extension created beforehand (i.e. as a temp file). @@ -949,12 +773,7 @@ class Page extends EventEmitter { return this._screenshotTaskQueue.postTask(this._screenshotTask.bind(this, screenshotType, options)); } - /** - * @param {"png"|"jpeg"} format - * @param {!ScreenshotOptions=} options - * @return {!Promise} - */ - async _screenshotTask(format, options) { + async _screenshotTask(format: 'png' | 'jpeg', options?: ScreenshotOptions): Promise { await this._client.send('Target.activateTarget', {targetId: this._target._targetId}); let clip = options.clip ? processClip(options.clip) : undefined; @@ -970,8 +789,7 @@ class Page extends EventEmitter { deviceScaleFactor = 1, isLandscape = false } = this._viewport || {}; - /** @type {!Protocol.Emulation.ScreenOrientation} */ - const screenOrientation = isLandscape ? {angle: 90, type: 'landscapePrimary'} : {angle: 0, type: 'portraitPrimary'}; + const screenOrientation: Protocol.Emulation.ScreenOrientation = isLandscape ? {angle: 90, type: 'landscapePrimary'} : {angle: 0, type: 'portraitPrimary'}; await this._client.send('Emulation.setDeviceMetricsOverride', {mobile: isMobile, width, height, deviceScaleFactor, screenOrientation}); } const shouldSetDefaultBackground = options.omitBackground && format === 'png'; @@ -989,7 +807,7 @@ class Page extends EventEmitter { await writeFileAsync(options.path, buffer); return buffer; - function processClip(clip) { + function processClip(clip: ScreenshotClip): ScreenshotClip & { scale: number } { const x = Math.round(clip.x); const y = Math.round(clip.y); const width = Math.round(clip.width + clip.x - x); @@ -998,11 +816,7 @@ class Page extends EventEmitter { } } - /** - * @param {!PDFOptions=} options - * @return {!Promise} - */ - async pdf(options = {}) { + async pdf(options: PDFOptions = {}): Promise { const { scale = 1, displayHeaderFooter = false, @@ -1019,7 +833,7 @@ class Page extends EventEmitter { let paperWidth = 8.5; let paperHeight = 11; if (options.format) { - const format = Page.PaperFormats[options.format.toLowerCase()]; + const format = paperFormats[options.format.toLowerCase()]; assert(format, 'Unknown paper format: ' + options.format); paperWidth = format.width; paperHeight = format.height; @@ -1053,17 +867,11 @@ class Page extends EventEmitter { return await helper.readProtocolStream(this._client, result.stream, path); } - /** - * @return {!Promise} - */ - async title() { + async title(): Promise { return this.mainFrame().title(); } - /** - * @param {!{runBeforeUnload: (boolean|undefined)}=} options - */ - async close(options = {runBeforeUnload: undefined}) { + async close(options: {runBeforeUnload?: boolean} = {runBeforeUnload: undefined}): Promise { assert(!!this._client._connection, 'Protocol error: Connection closed. Most likely the page has been closed.'); const runBeforeUnload = !!options.runBeforeUnload; if (runBeforeUnload) { @@ -1074,159 +882,76 @@ class Page extends EventEmitter { } } - /** - * @return {boolean} - */ - isClosed() { + isClosed(): boolean { return this._closed; } - /** - * @return {!Mouse} - */ - get mouse() { + get mouse(): Mouse { return this._mouse; } - /** - * @param {string} selector - * @param {!{delay?: number, button?: "left"|"right"|"middle", clickCount?: number}=} options - */ - click(selector, options = {}) { + click(selector: string, options: { + delay?: number; + button?: MouseButtonInput; + clickCount?: number; + } = {}): Promise { return this.mainFrame().click(selector, options); } - /** - * @param {string} selector - */ - focus(selector) { + focus(selector: string): Promise { return this.mainFrame().focus(selector); } - /** - * @param {string} selector - */ - hover(selector) { + hover(selector: string): Promise { return this.mainFrame().hover(selector); } - /** - * @param {string} selector - * @param {!Array} values - * @return {!Promise>} - */ - select(selector, ...values) { + select(selector: string, ...values: string[]): Promise { return this.mainFrame().select(selector, ...values); } - /** - * @param {string} selector - */ - tap(selector) { + tap(selector: string): Promise { return this.mainFrame().tap(selector); } - /** - * @param {string} selector - * @param {string} text - * @param {{delay: (number|undefined)}=} options - */ - type(selector, text, options) { + type(selector: string, text: string, options?: {delay: number}): Promise { return this.mainFrame().type(selector, text, options); } - /** - * @param {(string|number|Function)} selectorOrFunctionOrTimeout - * @param {!{visible?: boolean, hidden?: boolean, timeout?: number, polling?: string|number}=} options - * @param {!Array<*>} args - * @return {!Promise} - */ - waitFor(selectorOrFunctionOrTimeout, options = {}, ...args) { + waitFor(selectorOrFunctionOrTimeout: string | number | Function, options: { + visible?: boolean; + hidden?: boolean; + timeout?: number; + polling?: string|number; + } = {}, ...args: unknown[]): Promise { return this.mainFrame().waitFor(selectorOrFunctionOrTimeout, options, ...args); } - /** - * @param {string} selector - * @param {!{visible?: boolean, hidden?: boolean, timeout?: number}=} options - * @return {!Promise} - */ - waitForSelector(selector, options = {}) { + waitForSelector(selector: string, options: { + visible?: boolean; + hidden?: boolean; + timeout?: number; + } = {}): Promise { return this.mainFrame().waitForSelector(selector, options); } - /** - * @param {string} xpath - * @param {!{visible?: boolean, hidden?: boolean, timeout?: number}=} options - * @return {!Promise} - */ - waitForXPath(xpath, options = {}) { + waitForXPath(xpath: string, options: { + visible?: boolean; + hidden?: boolean; + timeout?: number; + } = {}): Promise { return this.mainFrame().waitForXPath(xpath, options); } - /** - * @param {Function} pageFunction - * @param {!{polling?: string|number, timeout?: number}=} options - * @param {!Array<*>} args - * @return {!Promise} - */ - waitForFunction(pageFunction, options = {}, ...args) { + waitForFunction(pageFunction: Function, options: { + timeout?: number; + polling?: string|number; + } = {}, ...args: unknown[]): Promise { return this.mainFrame().waitForFunction(pageFunction, options, ...args); } } -/** - * @typedef {Object} PDFOptions - * @property {number=} scale - * @property {boolean=} displayHeaderFooter - * @property {string=} headerTemplate - * @property {string=} footerTemplate - * @property {boolean=} printBackground - * @property {boolean=} landscape - * @property {string=} pageRanges - * @property {string=} format - * @property {string|number=} width - * @property {string|number=} height - * @property {boolean=} preferCSSPageSize - * @property {!{top?: string|number, bottom?: string|number, left?: string|number, right?: string|number}=} margin - * @property {string=} path - */ - -/** - * @typedef {Object} Metrics - * @property {number=} Timestamp - * @property {number=} Documents - * @property {number=} Frames - * @property {number=} JSEventListeners - * @property {number=} Nodes - * @property {number=} LayoutCount - * @property {number=} RecalcStyleCount - * @property {number=} LayoutDuration - * @property {number=} RecalcStyleDuration - * @property {number=} ScriptDuration - * @property {number=} TaskDuration - * @property {number=} JSHeapUsedSize - * @property {number=} JSHeapTotalSize - */ - -/** - * @typedef {Object} ScreenshotOptions - * @property {string=} type - * @property {string=} path - * @property {boolean=} fullPage - * @property {{x: number, y: number, width: number, height: number}=} clip - * @property {number=} quality - * @property {boolean=} omitBackground - * @property {string=} encoding - */ - -/** - * @typedef {Object} MediaFeature - * @property {string} name - * @property {string} value - */ - -/** @type {!Set} */ -const supportedMetrics = new Set([ +const supportedMetrics = new Set([ 'Timestamp', 'Documents', 'Frames', @@ -1242,20 +967,6 @@ const supportedMetrics = new Set([ 'JSHeapTotalSize', ]); -/** @enum {!{width: number, height: number}} */ -Page.PaperFormats = { - letter: {width: 8.5, height: 11}, - legal: {width: 8.5, height: 14}, - tabloid: {width: 11, height: 17}, - ledger: {width: 17, height: 11}, - a0: {width: 33.1, height: 46.8}, - a1: {width: 23.4, height: 33.1}, - a2: {width: 16.54, height: 23.4}, - a3: {width: 11.7, height: 16.54}, - a4: {width: 8.27, height: 11.7}, - a5: {width: 5.83, height: 8.27}, - a6: {width: 4.13, height: 5.83}, -}; const unitToPixels = { 'px': 1, @@ -1264,11 +975,7 @@ const unitToPixels = { 'mm': 3.78 }; -/** - * @param {(string|number|undefined)} parameter - * @return {(number|undefined)} - */ -function convertPrintParameterToInches(parameter) { +function convertPrintParameterToInches(parameter?: string|number): number | undefined { if (typeof parameter === 'undefined') return undefined; let pixels; @@ -1296,121 +1003,66 @@ function convertPrintParameterToInches(parameter) { return pixels / 96; } -/** - * @typedef {Object} Network.Cookie - * @property {string} name - * @property {string} value - * @property {string} domain - * @property {string} path - * @property {number} expires - * @property {number} size - * @property {boolean} httpOnly - * @property {boolean} secure - * @property {boolean} session - * @property {("Strict"|"Lax"|"Extended"|"None")=} sameSite - */ - - -/** - * @typedef {Object} Network.CookieParam - * @property {string} name - * @property {string} value - * @property {string=} url - * @property {string=} domain - * @property {string=} path - * @property {number=} expires - * @property {boolean=} httpOnly - * @property {boolean=} secure - * @property {("Strict"|"Lax")=} sameSite - */ +interface ConsoleMessageLocation { + url?: string; + lineNumber?: number; + columnNumber?: number; +} -/** - * @typedef {Object} ConsoleMessage.Location - * @property {string=} url - * @property {number=} lineNumber - * @property {number=} columnNumber - */ +export class ConsoleMessage { + _type: string; + _text: string; + _args: JSHandle[]; + _location: ConsoleMessageLocation; -class ConsoleMessage { - /** - * @param {string} type - * @param {string} text - * @param {!Array} args - * @param {ConsoleMessage.Location} location - */ - constructor(type, text, args, location = {}) { + constructor(type: string, text: string, args: JSHandle[], location: ConsoleMessageLocation = {}) { this._type = type; this._text = text; this._args = args; this._location = location; } - /** - * @return {string} - */ - type() { + type(): string { return this._type; } - /** - * @return {string} - */ - text() { + text(): string { return this._text; } - /** - * @return {!Array} - */ - args() { + args(): JSHandle[] { return this._args; } - /** - * @return {Object} - */ - location() { + location(): ConsoleMessageLocation { return this._location; } } -class FileChooser { - /** - * @param {CDPSession} client - * @param {ElementHandle} element - * @param {!Protocol.Page.fileChooserOpenedPayload} event - */ - constructor(client, element, event) { +export class FileChooser { + _client: CDPSession; + _element: ElementHandle; + _multiple: boolean; + _handled = false; + + constructor(client: CDPSession, element: ElementHandle, event: Protocol.Page.fileChooserOpenedPayload) { this._client = client; this._element = element; this._multiple = event.mode !== 'selectSingle'; - this._handled = false; } - /** - * @return {boolean} - */ - isMultiple() { + isMultiple(): boolean { return this._multiple; } - /** - * @param {!Array} filePaths - * @return {!Promise} - */ - async accept(filePaths) { + async accept(filePaths: string[]): Promise { assert(!this._handled, 'Cannot accept FileChooser which is already handled!'); this._handled = true; await this._element.uploadFile(...filePaths); } - /** - * @return {!Promise} - */ - async cancel() { + async cancel(): Promise { assert(!this._handled, 'Cannot cancel FileChooser which is already handled!'); this._handled = true; } } - -module.exports = {Page, ConsoleMessage, FileChooser}; diff --git a/src/Target.ts b/src/Target.ts index 834f861db6c7c..7069c18886f7c 100644 --- a/src/Target.ts +++ b/src/Target.ts @@ -29,7 +29,7 @@ export class Target { _ignoreHTTPSErrors: boolean; _defaultViewport?: Puppeteer.Viewport; _screenshotTaskQueue: TaskQueue; - _pagePromise?: Promise; + _pagePromise?: Promise; _workerPromise?: Promise; _initializedPromise: Promise; _initializedCallback: (x: boolean) => void; @@ -72,7 +72,7 @@ export class Target { return this._sessionFactory(); } - async page(): Promise { + async page(): Promise { if ((this._targetInfo.type === 'page' || this._targetInfo.type === 'background_page') && !this._pagePromise) { this._pagePromise = this._sessionFactory() .then(client => Page.create(client, this, this._ignoreHTTPSErrors, this._defaultViewport, this._screenshotTaskQueue)); diff --git a/src/externs.d.ts b/src/externs.d.ts index d7eb0a5b8492f..f7e8531454736 100644 --- a/src/externs.d.ts +++ b/src/externs.d.ts @@ -1,14 +1,7 @@ -import {Page as RealPage} from './Page.js'; import * as child_process from 'child_process'; declare global { module Puppeteer { - export class Page extends RealPage { } - /* TODO(jacktfranklin@): once DOMWorld, Page, and FrameManager are in TS - * we can remove this and instead use the type defined in LifeCycleWatcher - */ - export type PuppeteerLifeCycleEvent = 'load' | 'domcontentloaded' | 'networkidle0' | 'networkidle2'; - export interface ConnectionTransport { send(string); close(); diff --git a/utils/doclint/check_public_api/index.js b/utils/doclint/check_public_api/index.js index 5f344f3693ee5..b80fd185b77e9 100644 --- a/utils/doclint/check_public_api/index.js +++ b/utils/doclint/check_public_api/index.js @@ -359,6 +359,46 @@ function compareDocumentations(actual, expected) { actualName: 'Object', expectedName: 'BrowserFetcherOptions' }], + ['Method Page.authenticate() credentials', { + actualName: 'Object', + expectedName: 'Credentials' + }], + ['Method Page.emulateMediaFeatures() features', { + actualName: 'Array', + expectedName: 'Array' + }], + ['Method Page.goBack() options', { + actualName: 'Object', + expectedName: 'WaitForOptions' + }], + ['Method Page.goForward() options', { + actualName: 'Object', + expectedName: 'WaitForOptions' + }], + ['Method Page.reload() options', { + actualName: 'Object', + expectedName: 'WaitForOptions' + }], + ['Method Page.waitForNavigation() options', { + actualName: 'Object', + expectedName: 'WaitForOptions' + }], + ['Method Page.pdf() options', { + actualName: 'Object', + expectedName: 'PDFOptions' + }], + ['Method Page.screenshot() options', { + actualName: 'Object', + expectedName: 'ScreenshotOptions' + }], + ['Method Page.setContent() options', { + actualName: 'Object', + expectedName: 'WaitForOptions' + }], + ['Method Page.setCookie() ...cookies', { + actualName: '...Object', + expectedName: '...CookieParam' + }], ]); const expectedForSource = expectedNamingMismatches.get(source);