From 6053784188c8fbafff984d3d919c0852b353cbf9 Mon Sep 17 00:00:00 2001 From: Joel Einbinder Date: Tue, 31 Mar 2020 16:34:59 -0700 Subject: [PATCH] feat: add missing slowMo to launchPersistentContext (#1597) `slowMo` was missing in `launchPersistentContext`, and I refactored the types a bit. --- docs/api.md | 1 + src/browser.ts | 5 ----- src/server/browserType.ts | 13 +++++++++---- src/server/chromium.ts | 32 ++++++++++++++++++-------------- src/server/firefox.ts | 32 ++++++++++++++++++-------------- src/server/webkit.ts | 34 +++++++++++++++++++--------------- 6 files changed, 65 insertions(+), 52 deletions(-) diff --git a/docs/api.md b/docs/api.md index ec6530d721a45..2cecb5b05d342 100644 --- a/docs/api.md +++ b/docs/api.md @@ -3690,6 +3690,7 @@ const browser = await chromium.launch({ // Or 'firefox' or 'webkit'. - `dumpio` <[boolean]> Whether to pipe the browser process stdout and stderr into `process.stdout` and `process.stderr`. Defaults to `false`. - `env` <[Object]> Specify environment variables that will be visible to the browser. Defaults to `process.env`. - `devtools` <[boolean]> **Chromium-only** Whether to auto-open a Developer Tools panel for each tab. If this option is `true`, the `headless` option will be set `false`. + - `slowMo` <[number]> Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on. Defaults to 0. - returns: <[Promise]<[BrowserContext]>> Promise which resolves to the browser app instance. Launches browser instance that uses persistent storage located at `userDataDir`. diff --git a/src/browser.ts b/src/browser.ts index 4caabc079da8f..02cb42c305c07 100644 --- a/src/browser.ts +++ b/src/browser.ts @@ -27,11 +27,6 @@ export interface Browser extends platform.EventEmitterType { _setDebugFunction(debugFunction: (message: string) => void): void; } -export type ConnectOptions = { - slowMo?: number, - wsEndpoint: string -}; - export async function createPageInNewContext(browser: Browser, options?: BrowserContextOptions): Promise { const context = await browser.newContext(options); const page = await context.newPage(); diff --git a/src/server/browserType.ts b/src/server/browserType.ts index 7b76cf4dcc546..d0abf975ca01f 100644 --- a/src/server/browserType.ts +++ b/src/server/browserType.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { ConnectOptions } from '../browser'; import { BrowserContext } from '../browserContext'; import { BrowserServer } from './browserServer'; @@ -24,7 +23,7 @@ export type BrowserArgOptions = { devtools?: boolean, }; -export type LaunchOptions = BrowserArgOptions & { +type LaunchOptionsBase = BrowserArgOptions & { executablePath?: string, ignoreDefaultArgs?: boolean | string[], handleSIGINT?: boolean, @@ -39,11 +38,17 @@ export type LaunchOptions = BrowserArgOptions & { env?: {[key: string]: string} | undefined }; +export type ConnectOptions = { + wsEndpoint: string, + slowMo?: number +}; +export type LaunchOptions = LaunchOptionsBase & { slowMo?: number }; +export type LaunchServerOptions = LaunchOptionsBase & { port?: number }; export interface BrowserType { executablePath(): string; name(): string; - launch(options?: LaunchOptions & { slowMo?: number }): Promise; - launchServer(options?: LaunchOptions & { port?: number }): Promise; + launch(options?: LaunchOptions): Promise; + launchServer(options?: LaunchServerOptions): Promise; launchPersistentContext(userDataDir: string, options?: LaunchOptions): Promise; connect(options: ConnectOptions): Promise; } diff --git a/src/server/chromium.ts b/src/server/chromium.ts index 0751ca698a2ee..7cf456a898534 100644 --- a/src/server/chromium.ts +++ b/src/server/chromium.ts @@ -18,15 +18,15 @@ import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; -import { debugError, helper } from '../helper'; +import { debugError, helper, assert } from '../helper'; import { CRBrowser } from '../chromium/crBrowser'; import * as platform from '../platform'; import * as ws from 'ws'; import { launchProcess } from '../server/processLauncher'; import { kBrowserCloseMessageId } from '../chromium/crConnection'; import { PipeTransport } from './pipeTransport'; -import { LaunchOptions, BrowserArgOptions, BrowserType } from './browserType'; -import { ConnectOptions, LaunchType } from '../browser'; +import { LaunchOptions, BrowserArgOptions, BrowserType, ConnectOptions, LaunchServerOptions } from './browserType'; +import { LaunchType } from '../browser'; import { BrowserServer, WebSocketWrapper } from './browserServer'; import { Events } from '../events'; import { ConnectionTransport, ProtocolRequest } from '../transport'; @@ -45,28 +45,30 @@ export class Chromium implements BrowserType { return 'chromium'; } - async launch(options?: LaunchOptions & { slowMo?: number }): Promise { - if (options && (options as any).userDataDir) - throw new Error('userDataDir option is not supported in `browserType.launch`. Use `browserType.launchPersistentContext` instead'); + async launch(options: LaunchOptions = {}): Promise { + assert(!(options as any).userDataDir, 'userDataDir option is not supported in `browserType.launch`. Use `browserType.launchPersistentContext` instead'); const { browserServer, transport } = await this._launchServer(options, 'local'); - const browser = await CRBrowser.connect(transport!, false, options && options.slowMo); + const browser = await CRBrowser.connect(transport!, false, options.slowMo); (browser as any)['__server__'] = browserServer; return browser; } - async launchServer(options?: LaunchOptions & { port?: number }): Promise { - return (await this._launchServer(options, 'server', undefined, options && options.port)).browserServer; + async launchServer(options: LaunchServerOptions = {}): Promise { + return (await this._launchServer(options, 'server')).browserServer; } - async launchPersistentContext(userDataDir: string, options?: LaunchOptions): Promise { - const { timeout = 30000 } = options || {}; + async launchPersistentContext(userDataDir: string, options: LaunchOptions = {}): Promise { + const { + timeout = 30000, + slowMo = 0 + } = options; const { transport } = await this._launchServer(options, 'persistent', userDataDir); - const browser = await CRBrowser.connect(transport!, true); + const browser = await CRBrowser.connect(transport!, true, slowMo); await helper.waitWithTimeout(browser._firstPagePromise, 'first page', timeout); return browser._defaultContext; } - private async _launchServer(options: LaunchOptions = {}, launchType: LaunchType, userDataDir?: string, port?: number): Promise<{ browserServer: BrowserServer, transport?: ConnectionTransport }> { + private async _launchServer(options: LaunchServerOptions, launchType: LaunchType, userDataDir?: string): Promise<{ browserServer: BrowserServer, transport?: ConnectionTransport }> { const { ignoreDefaultArgs = false, args = [], @@ -76,7 +78,9 @@ export class Chromium implements BrowserType { handleSIGINT = true, handleSIGTERM = true, handleSIGHUP = true, + port = 0, } = options; + assert(!port || launchType === 'server', 'Cannot specify a port without launching as a server.'); let temporaryUserDataDir: string | null = null; if (!userDataDir) { @@ -124,7 +128,7 @@ export class Chromium implements BrowserType { let transport: PipeTransport | undefined = undefined; let browserServer: BrowserServer | undefined = undefined; transport = new PipeTransport(launchedProcess.stdio[3] as NodeJS.WritableStream, launchedProcess.stdio[4] as NodeJS.ReadableStream, () => browserServer!.close()); - browserServer = new BrowserServer(launchedProcess, gracefullyClose, launchType === 'server' ? wrapTransportWithWebSocket(transport, port || 0) : null); + browserServer = new BrowserServer(launchedProcess, gracefullyClose, launchType === 'server' ? wrapTransportWithWebSocket(transport, port) : null); return { browserServer, transport }; } diff --git a/src/server/firefox.ts b/src/server/firefox.ts index ba0cb663590e6..39dc323d6e8b2 100644 --- a/src/server/firefox.ts +++ b/src/server/firefox.ts @@ -19,16 +19,16 @@ import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; import * as ws from 'ws'; -import { ConnectOptions, LaunchType } from '../browser'; +import { LaunchType } from '../browser'; import { BrowserContext } from '../browserContext'; import { TimeoutError } from '../errors'; import { Events } from '../events'; import { FFBrowser } from '../firefox/ffBrowser'; import { kBrowserCloseMessageId } from '../firefox/ffConnection'; -import { debugError, helper } from '../helper'; +import { debugError, helper, assert } from '../helper'; import * as platform from '../platform'; import { BrowserServer, WebSocketWrapper } from './browserServer'; -import { BrowserArgOptions, BrowserType, LaunchOptions } from './browserType'; +import { BrowserArgOptions, BrowserType, LaunchOptions, LaunchServerOptions, ConnectOptions } from './browserType'; import { launchProcess, waitForLine } from './processLauncher'; import { ConnectionTransport, SequenceNumberMixer } from '../transport'; @@ -47,12 +47,11 @@ export class Firefox implements BrowserType { return 'firefox'; } - async launch(options?: LaunchOptions & { slowMo?: number }): Promise { - if (options && (options as any).userDataDir) - throw new Error('userDataDir option is not supported in `browserType.launch`. Use `browserType.launchPersistentContext` instead'); + async launch(options: LaunchOptions = {}): Promise { + assert(!(options as any).userDataDir, 'userDataDir option is not supported in `browserType.launch`. Use `browserType.launchPersistentContext` instead'); const browserServer = await this._launchServer(options, 'local'); const browser = await platform.connectToWebsocket(browserServer.wsEndpoint()!, transport => { - return FFBrowser.connect(transport, false, options && options.slowMo); + return FFBrowser.connect(transport, false, options.slowMo); }); // Hack: for typical launch scenario, ensure that close waits for actual process termination. browser.close = () => browserServer.close(); @@ -60,15 +59,18 @@ export class Firefox implements BrowserType { return browser; } - async launchServer(options?: LaunchOptions & { port?: number }): Promise { - return await this._launchServer(options, 'server', undefined, options && options.port); + async launchServer(options: LaunchServerOptions = {}): Promise { + return await this._launchServer(options, 'server'); } - async launchPersistentContext(userDataDir: string, options?: LaunchOptions): Promise { - const { timeout = 30000 } = options || {}; + async launchPersistentContext(userDataDir: string, options: LaunchOptions = {}): Promise { + const { + timeout = 30000, + slowMo = 0, + } = options; const browserServer = await this._launchServer(options, 'persistent', userDataDir); const browser = await platform.connectToWebsocket(browserServer.wsEndpoint()!, transport => { - return FFBrowser.connect(transport, true); + return FFBrowser.connect(transport, true, slowMo); }); await helper.waitWithTimeout(browser._firstPagePromise, 'first page', timeout); // Hack: for typical launch scenario, ensure that close waits for actual process termination. @@ -77,7 +79,7 @@ export class Firefox implements BrowserType { return browserContext; } - private async _launchServer(options: LaunchOptions = {}, launchType: LaunchType, userDataDir?: string, port?: number): Promise { + private async _launchServer(options: LaunchServerOptions, launchType: LaunchType, userDataDir?: string): Promise { const { ignoreDefaultArgs = false, args = [], @@ -88,7 +90,9 @@ export class Firefox implements BrowserType { handleSIGINT = true, handleSIGTERM = true, timeout = 30000, + port = 0, } = options; + assert(!port || launchType === 'server', 'Cannot specify a port without launching as a server.'); const firefoxArguments = []; @@ -145,7 +149,7 @@ export class Firefox implements BrowserType { let browserServer: BrowserServer | undefined = undefined; let browserWSEndpoint: string | undefined = undefined; - const webSocketWrapper = launchType === 'server' ? (await platform.connectToWebsocket(innerEndpoint, t => wrapTransportWithWebSocket(t, port || 0))) : new WebSocketWrapper(innerEndpoint, []); + const webSocketWrapper = launchType === 'server' ? (await platform.connectToWebsocket(innerEndpoint, t => wrapTransportWithWebSocket(t, port))) : new WebSocketWrapper(innerEndpoint, []); browserWSEndpoint = webSocketWrapper.wsEndpoint; browserServer = new BrowserServer(launchedProcess, gracefullyClose, webSocketWrapper); return browserServer; diff --git a/src/server/webkit.ts b/src/server/webkit.ts index 1027a49799230..6ff39996c90ee 100644 --- a/src/server/webkit.ts +++ b/src/server/webkit.ts @@ -22,12 +22,12 @@ import * as fs from 'fs'; import * as path from 'path'; import * as platform from '../platform'; import * as os from 'os'; -import { debugError, helper } from '../helper'; +import { debugError, helper, assert } from '../helper'; import { kBrowserCloseMessageId } from '../webkit/wkConnection'; -import { LaunchOptions, BrowserArgOptions, BrowserType } from './browserType'; +import { LaunchOptions, BrowserArgOptions, BrowserType, LaunchServerOptions, ConnectOptions } from './browserType'; import { ConnectionTransport, SequenceNumberMixer } from '../transport'; import * as ws from 'ws'; -import { ConnectOptions, LaunchType } from '../browser'; +import { LaunchType } from '../browser'; import { BrowserServer, WebSocketWrapper } from './browserServer'; import { Events } from '../events'; import { BrowserContext } from '../browserContext'; @@ -45,28 +45,30 @@ export class WebKit implements BrowserType { return 'webkit'; } - async launch(options?: LaunchOptions & { slowMo?: number }): Promise { - if (options && (options as any).userDataDir) - throw new Error('userDataDir option is not supported in `browserType.launch`. Use `browserType.launchPersistentContext` instead'); + async launch(options: LaunchOptions = {}): Promise { + assert(!(options as any).userDataDir, 'userDataDir option is not supported in `browserType.launch`. Use `browserType.launchPersistentContext` instead'); const { browserServer, transport } = await this._launchServer(options, 'local'); - const browser = await WKBrowser.connect(transport!, options && options.slowMo); + const browser = await WKBrowser.connect(transport!, options.slowMo); (browser as any)['__server__'] = browserServer; return browser; } - async launchServer(options?: LaunchOptions & { port?: number }): Promise { - return (await this._launchServer(options, 'server', undefined, options && options.port)).browserServer; + async launchServer(options: LaunchServerOptions = {}): Promise { + return (await this._launchServer(options, 'server')).browserServer; } - async launchPersistentContext(userDataDir: string, options?: LaunchOptions): Promise { - const { timeout = 30000 } = options || {}; + async launchPersistentContext(userDataDir: string, options: LaunchOptions = {}): Promise { + const { + timeout = 30000, + slowMo = 0, + } = options; const { transport } = await this._launchServer(options, 'persistent', userDataDir); - const browser = await WKBrowser.connect(transport!, undefined, true); + const browser = await WKBrowser.connect(transport!, slowMo, true); await helper.waitWithTimeout(browser._waitForFirstPageTarget(), 'first page', timeout); return browser._defaultContext; } - private async _launchServer(options: LaunchOptions = {}, launchType: LaunchType, userDataDir?: string, port?: number): Promise<{ browserServer: BrowserServer, transport?: ConnectionTransport }> { + private async _launchServer(options: LaunchServerOptions, launchType: LaunchType, userDataDir?: string): Promise<{ browserServer: BrowserServer, transport?: ConnectionTransport }> { const { ignoreDefaultArgs = false, args = [], @@ -76,7 +78,9 @@ export class WebKit implements BrowserType { handleSIGINT = true, handleSIGTERM = true, handleSIGHUP = true, + port = 0, } = options; + assert(!port || launchType === 'server', 'Cannot specify a port without launching as a server.'); let temporaryUserDataDir: string | null = null; if (!userDataDir) { @@ -86,9 +90,9 @@ export class WebKit implements BrowserType { const webkitArguments = []; if (!ignoreDefaultArgs) - webkitArguments.push(...this._defaultArgs(options, launchType, userDataDir!, port || 0)); + webkitArguments.push(...this._defaultArgs(options, launchType, userDataDir!, port)); else if (Array.isArray(ignoreDefaultArgs)) - webkitArguments.push(...this._defaultArgs(options, launchType, userDataDir!, port || 0).filter(arg => ignoreDefaultArgs.indexOf(arg) === -1)); + webkitArguments.push(...this._defaultArgs(options, launchType, userDataDir!, port).filter(arg => ignoreDefaultArgs.indexOf(arg) === -1)); else webkitArguments.push(...args);