From 890c2151423ecc8026471103ff377d684e4c38aa Mon Sep 17 00:00:00 2001 From: Jack Franklin Date: Tue, 5 May 2020 09:36:44 +0100 Subject: [PATCH] chore: migrate src/Puppeteer to TypeScript (#5789) * chore: migrate src/Puppeteer to TypeScript. * fix api js --- index.js | 2 +- src/Browser.ts | 9 +- src/BrowserFetcher.ts | 4 - src/DeviceDescriptors.ts | 5 +- src/Errors.ts | 6 + src/Launcher.ts | 5 +- src/Puppeteer.js | 174 ------------------------ src/Puppeteer.ts | 131 ++++++++++++++++++ src/api.js | 2 +- src/externs.d.ts | 42 +----- test/launcher.spec.js | 4 +- utils/doclint/check_public_api/index.js | 4 + 12 files changed, 154 insertions(+), 234 deletions(-) delete mode 100644 src/Puppeteer.js create mode 100644 src/Puppeteer.ts diff --git a/index.js b/index.js index d74c3e0acd74a..fe2c26af7e418 100644 --- a/index.js +++ b/index.js @@ -25,7 +25,7 @@ for (const className in api) { // Expose alias for deprecated method. Page.prototype.emulateMedia = Page.prototype.emulateMediaType; -const Puppeteer = require('./lib/Puppeteer'); +const {Puppeteer} = require('./lib/Puppeteer'); const packageJson = require('./package.json'); let preferredRevision = packageJson.puppeteer.chromium_revision; const isPuppeteerCore = packageJson.name === 'puppeteer-core'; diff --git a/src/Browser.ts b/src/Browser.ts index 948aa7864a64e..6b137aef678cc 100644 --- a/src/Browser.ts +++ b/src/Browser.ts @@ -20,18 +20,19 @@ import * as EventEmitter from 'events'; import {TaskQueue} from './TaskQueue'; import {Events} from './Events'; import {Connection} from './Connection'; +import {ChildProcess} from 'child_process'; type BrowserCloseCallback = () => Promise | void; export class Browser extends EventEmitter { - static async create(connection: Connection, contextIds: string[], ignoreHTTPSErrors: boolean, defaultViewport?: Puppeteer.Viewport, process?: Puppeteer.ChildProcess, closeCallback?: BrowserCloseCallback): Promise { + static async create(connection: Connection, contextIds: string[], ignoreHTTPSErrors: boolean, defaultViewport?: Puppeteer.Viewport, process?: ChildProcess, closeCallback?: BrowserCloseCallback): Promise { const browser = new Browser(connection, contextIds, ignoreHTTPSErrors, defaultViewport, process, closeCallback); await connection.send('Target.setDiscoverTargets', {discover: true}); return browser; } _ignoreHTTPSErrors: boolean; _defaultViewport?: Puppeteer.Viewport; - _process?: Puppeteer.ChildProcess; + _process?: ChildProcess; _screenshotTaskQueue = new TaskQueue(); _connection: Connection; _closeCallback: BrowserCloseCallback; @@ -40,7 +41,7 @@ export class Browser extends EventEmitter { // TODO: once Target is in TypeScript we can type this properly. _targets: Map; - constructor(connection: Connection, contextIds: string[], ignoreHTTPSErrors: boolean, defaultViewport?: Puppeteer.Viewport, process?: Puppeteer.ChildProcess, closeCallback?: BrowserCloseCallback) { + constructor(connection: Connection, contextIds: string[], ignoreHTTPSErrors: boolean, defaultViewport?: Puppeteer.Viewport, process?: ChildProcess, closeCallback?: BrowserCloseCallback) { super(); this._ignoreHTTPSErrors = ignoreHTTPSErrors; this._defaultViewport = defaultViewport; @@ -61,7 +62,7 @@ export class Browser extends EventEmitter { this._connection.on('Target.targetInfoChanged', this._targetInfoChanged.bind(this)); } - process(): Puppeteer.ChildProcess | null { + process(): ChildProcess | null { return this._process; } diff --git a/src/BrowserFetcher.ts b/src/BrowserFetcher.ts index b2b2b9cae982a..b5fc47565ccca 100644 --- a/src/BrowserFetcher.ts +++ b/src/BrowserFetcher.ts @@ -99,10 +99,6 @@ function existsAsync(filePath: string): Promise { }); } -/** - * @typedef {Object} BrowserFetcher.Options - */ - export interface BrowserFetcherOptions { platform?: Platform; product?: string; diff --git a/src/DeviceDescriptors.ts b/src/DeviceDescriptors.ts index 2db6452de57e3..c37d8171c24c6 100644 --- a/src/DeviceDescriptors.ts +++ b/src/DeviceDescriptors.ts @@ -882,7 +882,7 @@ const devices: Device[] = [ } ]; -type DevicesMap = { +export type DevicesMap = { [name: string]: Device; }; @@ -891,5 +891,4 @@ const devicesMap: DevicesMap = {}; for (const device of devices) devicesMap[device.name] = device; - -export = devicesMap; +export {devicesMap}; diff --git a/src/Errors.ts b/src/Errors.ts index ae619a99bc859..48bf3f2a830d7 100644 --- a/src/Errors.ts +++ b/src/Errors.ts @@ -23,3 +23,9 @@ class CustomError extends Error { } export class TimeoutError extends CustomError {} + +export type PuppeteerErrors = Record; + +export const puppeteerErrors: PuppeteerErrors = { + TimeoutError, +}; diff --git a/src/Launcher.ts b/src/Launcher.ts index df2b89867f724..dfb67d7cd7cee 100644 --- a/src/Launcher.ts +++ b/src/Launcher.ts @@ -375,9 +375,6 @@ class ChromeLauncher implements ProductLauncher { } -/** - * @implements {!Puppeteer.ProductLauncher} - */ class FirefoxLauncher implements ProductLauncher { _projectRoot: string; _preferredRevision: string; @@ -738,7 +735,7 @@ class FirefoxLauncher implements ProductLauncher { } -function waitForWSEndpoint(browserProcess: Puppeteer.ChildProcess, timeout: number, preferredRevision: string): Promise { +function waitForWSEndpoint(browserProcess: childProcess.ChildProcess, timeout: number, preferredRevision: string): Promise { return new Promise((resolve, reject) => { const rl = readline.createInterface({input: browserProcess.stderr}); let stderr = ''; diff --git a/src/Puppeteer.js b/src/Puppeteer.js deleted file mode 100644 index 6a8d8bdf7f6de..0000000000000 --- a/src/Puppeteer.js +++ /dev/null @@ -1,174 +0,0 @@ -/** - * Copyright 2017 Google Inc. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -const {default: Launcher} = require('./Launcher'); -const {BrowserFetcher} = require('./BrowserFetcher'); -const Errors = require('./Errors'); -const DeviceDescriptors = require('./DeviceDescriptors'); -// Import used as typedef -// eslint-disable-next-line no-unused-vars -const {Browser} = require('./Browser'); -const QueryHandler = require('./QueryHandler'); - -module.exports = class { - /** - * @param {string} projectRoot - * @param {string} preferredRevision - * @param {boolean} isPuppeteerCore - * @param {string} productName - */ - constructor(projectRoot, preferredRevision, isPuppeteerCore, productName) { - this._projectRoot = projectRoot; - this._preferredRevision = preferredRevision; - this._isPuppeteerCore = isPuppeteerCore; - // track changes to Launcher configuration via options or environment variables - this.__productName = productName; - } - - /** - * @param {!(Puppeteer.LaunchOptions & Puppeteer.ChromeArgOptions & Puppeteer.BrowserOptions & {product?: string, extraPrefsFirefox?: !object})=} options - * @return {!Promise} - */ - launch(options = {}) { - if (options.product) - this._productName = options.product; - return this._launcher.launch(options); - } - - /** - * @param {!(Puppeteer.BrowserOptions & {browserWSEndpoint?: string, browserURL?: string, transport?: !Puppeteer.ConnectionTransport}) & {product?: string}=} options - * @return {!Promise} - */ - connect(options) { - if (options.product) - this._productName = options.product; - return this._launcher.connect(options); - } - - /** - * @param {string} name - */ - set _productName(name) { - if (this.__productName !== name) - this._changedProduct = true; - this.__productName = name; - } - - /** - * @return {string} - */ - get _productName() { - return this.__productName; - } - - /** - * @return {string} - */ - executablePath() { - return this._launcher.executablePath(); - } - - /** - * @return {!Puppeteer.ProductLauncher} - */ - get _launcher() { - if (!this._lazyLauncher || this._lazyLauncher.product !== this._productName || this._changedProduct) { - // @ts-ignore - const packageJson = require('../package.json'); - switch (this._productName) { - case 'firefox': - this._preferredRevision = packageJson.puppeteer.firefox_revision; - break; - case 'chrome': - default: - this._preferredRevision = packageJson.puppeteer.chromium_revision; - } - this._changedProduct = false; - this._lazyLauncher = Launcher(this._projectRoot, this._preferredRevision, this._isPuppeteerCore, this._productName); - } - return this._lazyLauncher; - } - - /** - * @return {string} - */ - get product() { - return this._launcher.product; - } - - /** - * @return {Object} - */ - get devices() { - return DeviceDescriptors; - } - - /** - * @return {Object} - */ - get errors() { - return Errors; - } - - /** - * @param {!Puppeteer.ChromeArgOptions=} options - * @return {!Array} - */ - defaultArgs(options) { - return this._launcher.defaultArgs(options); - } - - /** TODO(jacktfranklin@): Once this file is TS we can type this - * using the BrowserFectcherOptions interface. - */ - - /** - * @typedef {Object} BrowserFetcherOptions - * @property {('linux'|'mac'|'win32'|'win64')=} platform - * @property {('chrome'|'firefox')=} product - * @property {string=} path - * @property {string=} host - */ - /** - * @param {!BrowserFetcherOptions} options - * @return {!BrowserFetcher} - */ - createBrowserFetcher(options) { - return new BrowserFetcher(this._projectRoot, options); - } - - /** - * @param {string} name - * @param {!Function} queryHandler - */ - __experimental_registerCustomQueryHandler(name, queryHandler) { - QueryHandler.registerCustomQueryHandler(name, queryHandler); - } - - /** - * @param {string} name - */ - __experimental_unregisterCustomQueryHandler(name) { - QueryHandler.unregisterCustomQueryHandler(name); - } - - __experimental_customQueryHandlers() { - return QueryHandler.customQueryHandlers(); - } - - __experimental_clearQueryHandlers() { - QueryHandler.clearQueryHandlers(); - } -}; diff --git a/src/Puppeteer.ts b/src/Puppeteer.ts new file mode 100644 index 0000000000000..1c59b768d8f0b --- /dev/null +++ b/src/Puppeteer.ts @@ -0,0 +1,131 @@ +/** + * Copyright 2017 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import Launcher from './Launcher'; +import type {LaunchOptions, ChromeArgOptions, BrowserOptions, ProductLauncher} from './Launcher'; +import {BrowserFetcher, BrowserFetcherOptions} from './BrowserFetcher'; +import {puppeteerErrors, PuppeteerErrors} from './Errors'; + +import {devicesMap} from './DeviceDescriptors'; +import type {DevicesMap} from './/DeviceDescriptors'; +import {Browser} from './Browser'; +import * as QueryHandler from './QueryHandler'; + +export class Puppeteer { + _projectRoot: string; + _preferredRevision: string; + _isPuppeteerCore: boolean; + _changedProduct = false; + __productName: string; + _lazyLauncher: ProductLauncher; + + constructor(projectRoot: string, preferredRevision: string, isPuppeteerCore: boolean, productName: string) { + this._projectRoot = projectRoot; + this._preferredRevision = preferredRevision; + this._isPuppeteerCore = isPuppeteerCore; + // track changes to Launcher configuration via options or environment variables + this.__productName = productName; + } + + launch(options: LaunchOptions & ChromeArgOptions & BrowserOptions & {product?: string; extraPrefsFirefox?: {}} = {}): Promise { + if (options.product) + this._productName = options.product; + return this._launcher.launch(options); + } + + connect(options: BrowserOptions & { + browserWSEndpoint?: string; + browserURL?: string; + transport?: Puppeteer.ConnectionTransport; + product?: string; + }): Promise { + if (options.product) + this._productName = options.product; + return this._launcher.connect(options); + } + + set _productName(name: string) { + if (this.__productName !== name) + this._changedProduct = true; + this.__productName = name; + } + + get _productName(): string { + return this.__productName; + } + + executablePath(): string { + return this._launcher.executablePath(); + } + + get _launcher(): ProductLauncher { + if (!this._lazyLauncher || this._lazyLauncher.product !== this._productName || this._changedProduct) { + // @ts-ignore + // eslint-disable-next-line @typescript-eslint/no-var-requires + const packageJson = require('../package.json'); + switch (this._productName) { + case 'firefox': + this._preferredRevision = packageJson.puppeteer.firefox_revision; + break; + case 'chrome': + default: + this._preferredRevision = packageJson.puppeteer.chromium_revision; + } + this._changedProduct = false; + this._lazyLauncher = Launcher(this._projectRoot, this._preferredRevision, this._isPuppeteerCore, this._productName); + } + return this._lazyLauncher; + } + + get product(): string { + return this._launcher.product; + } + + get devices(): DevicesMap { + return devicesMap; + } + + get errors(): PuppeteerErrors { + return puppeteerErrors; + } + + defaultArgs(options: ChromeArgOptions): string[] { + return this._launcher.defaultArgs(options); + } + + createBrowserFetcher(options: BrowserFetcherOptions): BrowserFetcher { + return new BrowserFetcher(this._projectRoot, options); + } + + // eslint-disable-next-line @typescript-eslint/camelcase + __experimental_registerCustomQueryHandler(name: string, queryHandler: QueryHandler.QueryHandler): void { + QueryHandler.registerCustomQueryHandler(name, queryHandler); + } + + // eslint-disable-next-line @typescript-eslint/camelcase + __experimental_unregisterCustomQueryHandler(name: string): void { + QueryHandler.unregisterCustomQueryHandler(name); + } + + // eslint-disable-next-line @typescript-eslint/camelcase + __experimental_customQueryHandlers(): Map { + return QueryHandler.customQueryHandlers(); + } + + // eslint-disable-next-line @typescript-eslint/camelcase + __experimental_clearQueryHandlers(): void { + QueryHandler.clearQueryHandlers(); + } +} diff --git a/src/api.js b/src/api.js index 074431ae2a6a5..c9f21edd0f113 100644 --- a/src/api.js +++ b/src/api.js @@ -31,7 +31,7 @@ module.exports = { Keyboard: require('./Input').Keyboard, Mouse: require('./Input').Mouse, Page: require('./Page').Page, - Puppeteer: require('./Puppeteer'), + Puppeteer: require('./Puppeteer').Puppeteer, Request: require('./NetworkManager').Request, Response: require('./NetworkManager').Response, SecurityDetails: require('./NetworkManager').SecurityDetails, diff --git a/src/externs.d.ts b/src/externs.d.ts index c1473757e6d43..d7eb0a5b8492f 100644 --- a/src/externs.d.ts +++ b/src/externs.d.ts @@ -1,10 +1,9 @@ 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 */ @@ -17,45 +16,6 @@ declare global { onclose?: () => void, } - /* TODO(jacktfranklin@): these are duplicated from Launcher.ts. - * Once src/Puppeteer is migrated to TypeScript it can use those defs - * and we can delete these. - */ - export interface ProductLauncher { - launch(object) - connect(object) - executablePath: () => string, - defaultArgs(object) - product:string, - } - - export interface ChromeArgOptions { - headless?: boolean; - args?: string[]; - userDataDir?: string; - devtools?: boolean; - } - - export interface LaunchOptions { - executablePath?: string; - ignoreDefaultArgs?: boolean | string[]; - handleSIGINT?: boolean; - handleSIGTERM?: boolean; - handleSIGHUP?: boolean; - timeout?: number; - dumpio?: boolean; - env?: Record; - pipe?: boolean; - } - - export interface BrowserOptions { - ignoreHTTPSErrors?: boolean; - defaultViewport?: Puppeteer.Viewport; - slowMo?: number; - } - - export interface ChildProcess extends child_process.ChildProcess { } - export type Viewport = { width: number; height: number; diff --git a/test/launcher.spec.js b/test/launcher.spec.js index a9d05a8def776..71852aa095a00 100644 --- a/test/launcher.spec.js +++ b/test/launcher.spec.js @@ -496,8 +496,8 @@ describe('Launcher specs', function() { }); it('should require top-level DeviceDescriptors', async() => { const {puppeteer, puppeteerPath} = getTestState(); - const Devices = require(path.join(puppeteerPath, '/DeviceDescriptors')); - expect(Devices['iPhone 6']).toBe(puppeteer.devices['iPhone 6']); + const {devicesMap} = require(path.join(puppeteerPath, '/DeviceDescriptors')); + expect(devicesMap['iPhone 6']).toBe(puppeteer.devices['iPhone 6']); }); }); diff --git a/utils/doclint/check_public_api/index.js b/utils/doclint/check_public_api/index.js index b1cb9c3728ebe..5f344f3693ee5 100644 --- a/utils/doclint/check_public_api/index.js +++ b/utils/doclint/check_public_api/index.js @@ -355,6 +355,10 @@ function compareDocumentations(actual, expected) { actualName: 'Array', expectedName: 'Array' }], + ['Method Puppeteer.createBrowserFetcher() options', { + actualName: 'Object', + expectedName: 'BrowserFetcherOptions' + }], ]); const expectedForSource = expectedNamingMismatches.get(source);