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);