mirror of
				https://github.com/microsoft/playwright.git
				synced 2025-06-26 21:40:17 +00:00 
			
		
		
		
	feat(rpc): use rpc protocol for browserType.connect (#3380)
This commit is contained in:
		
							parent
							
								
									a4eb86c3df
								
							
						
					
					
						commit
						18b2cf5ec7
					
				
							
								
								
									
										147
									
								
								src/rpc/browserServerImpl.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										147
									
								
								src/rpc/browserServerImpl.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,147 @@ | |||||||
|  | /** | ||||||
|  |  * Copyright (c) Microsoft Corporation. | ||||||
|  |  * | ||||||
|  |  * 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 { LaunchServerOptions } from './client/types'; | ||||||
|  | import { BrowserTypeBase } from '../server/browserType'; | ||||||
|  | import * as ws from 'ws'; | ||||||
|  | import { helper } from '../helper'; | ||||||
|  | import { BrowserBase } from '../browser'; | ||||||
|  | import { ChildProcess } from 'child_process'; | ||||||
|  | import { Events } from '../events'; | ||||||
|  | import { EventEmitter } from 'ws'; | ||||||
|  | import { DispatcherScope, DispatcherConnection } from './server/dispatcher'; | ||||||
|  | import { BrowserTypeDispatcher } from './server/browserTypeDispatcher'; | ||||||
|  | import { BrowserDispatcher } from './server/browserDispatcher'; | ||||||
|  | import { BrowserContextDispatcher } from './server/browserContextDispatcher'; | ||||||
|  | import { BrowserNewContextParams, BrowserContextChannel } from './channels'; | ||||||
|  | import { BrowserServerLauncher, BrowserServer } from './client/browserType'; | ||||||
|  | 
 | ||||||
|  | export class BrowserServerLauncherImpl implements BrowserServerLauncher { | ||||||
|  |   private _browserType: BrowserTypeBase; | ||||||
|  | 
 | ||||||
|  |   constructor(browserType: BrowserTypeBase) { | ||||||
|  |     this._browserType = browserType; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async launchServer(options: LaunchServerOptions = {}): Promise<BrowserServerImpl> { | ||||||
|  |     const browser = await this._browserType.launch(options); | ||||||
|  |     return new BrowserServerImpl(this._browserType, browser as BrowserBase, options.port); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export class BrowserServerImpl extends EventEmitter implements BrowserServer { | ||||||
|  |   private _server: ws.Server; | ||||||
|  |   private _browserType: BrowserTypeBase; | ||||||
|  |   private _browser: BrowserBase; | ||||||
|  |   private _wsEndpoint: string; | ||||||
|  |   private _process: ChildProcess; | ||||||
|  | 
 | ||||||
|  |   constructor(browserType: BrowserTypeBase, browser: BrowserBase, port: number = 0) { | ||||||
|  |     super(); | ||||||
|  | 
 | ||||||
|  |     this._browserType = browserType; | ||||||
|  |     this._browser = browser; | ||||||
|  | 
 | ||||||
|  |     const token = helper.guid(); | ||||||
|  |     this._server = new ws.Server({ port }); | ||||||
|  |     const address = this._server.address(); | ||||||
|  |     this._wsEndpoint = typeof address === 'string' ? `${address}/${token}` : `ws://127.0.0.1:${address.port}/${token}`; | ||||||
|  |     const browserServer = browser._options.ownedServer!; | ||||||
|  |     this._process = browserServer.process(); | ||||||
|  | 
 | ||||||
|  |     this._server.on('connection', (socket: ws, req) => { | ||||||
|  |       if (req.url !== '/' + token) { | ||||||
|  |         socket.close(); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |       this._clientAttached(socket); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     browserServer.on(Events.BrowserServer.Close, (exitCode, signal) => { | ||||||
|  |       this._server.close(); | ||||||
|  |       this.emit('close', exitCode, signal); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   process(): ChildProcess { | ||||||
|  |     return this._process; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   wsEndpoint(): string { | ||||||
|  |     return this._wsEndpoint; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async close(): Promise<void> { | ||||||
|  |     const browserServer = this._browser._options.ownedServer!; | ||||||
|  |     await browserServer.close(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async kill(): Promise<void> { | ||||||
|  |     const browserServer = this._browser._options.ownedServer!; | ||||||
|  |     await browserServer.kill(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   private _clientAttached(socket: ws) { | ||||||
|  |     const connection = new DispatcherConnection(); | ||||||
|  |     connection.onmessage = message => { | ||||||
|  |       if (socket.readyState !== ws.CLOSING) | ||||||
|  |         socket.send(JSON.stringify(message)); | ||||||
|  |     }; | ||||||
|  |     socket.on('message', (message: string) => { | ||||||
|  |       connection.dispatch(JSON.parse(Buffer.from(message).toString())); | ||||||
|  |     }); | ||||||
|  |     socket.on('error', () => {}); | ||||||
|  |     const browserType = new BrowserTypeDispatcher(connection.rootDispatcher(), this._browserType); | ||||||
|  |     const browser = new ConnectedBrowser(browserType._scope, this._browser); | ||||||
|  |     socket.on('close', () => { | ||||||
|  |       // Avoid sending any more messages over closed socket.
 | ||||||
|  |       connection.onmessage = () => {}; | ||||||
|  |       // Cleanup contexts upon disconnect.
 | ||||||
|  |       browser.close().catch(e => {}); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | class ConnectedBrowser extends BrowserDispatcher { | ||||||
|  |   private _contexts: BrowserContextDispatcher[] = []; | ||||||
|  |   _closed = false; | ||||||
|  | 
 | ||||||
|  |   constructor(scope: DispatcherScope, browser: BrowserBase) { | ||||||
|  |     super(scope, browser, 'connectedBrowser'); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async newContext(params: BrowserNewContextParams): Promise<{ context: BrowserContextChannel }> { | ||||||
|  |     const result = await super.newContext(params); | ||||||
|  |     this._contexts.push(result.context as BrowserContextDispatcher); | ||||||
|  |     return result; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async close(): Promise<void> { | ||||||
|  |     // Only close our own contexts.
 | ||||||
|  |     await Promise.all(this._contexts.map(context => context.close())); | ||||||
|  |     this._didClose(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   _didClose() { | ||||||
|  |     if (!this._closed) { | ||||||
|  |       // We come here multiple times:
 | ||||||
|  |       // - from ConnectedBrowser.close();
 | ||||||
|  |       // - from underlying Browser.on('close').
 | ||||||
|  |       this._closed = true; | ||||||
|  |       super._didClose(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
| @ -141,23 +141,9 @@ export type BrowserTypeInitializer = { | |||||||
|   name: string, |   name: string, | ||||||
| }; | }; | ||||||
| export interface BrowserTypeChannel extends Channel { | export interface BrowserTypeChannel extends Channel { | ||||||
|   connect(params: BrowserTypeConnectParams): Promise<BrowserTypeConnectResult>; |  | ||||||
|   launch(params: BrowserTypeLaunchParams): Promise<BrowserTypeLaunchResult>; |   launch(params: BrowserTypeLaunchParams): Promise<BrowserTypeLaunchResult>; | ||||||
|   launchServer(params: BrowserTypeLaunchServerParams): Promise<BrowserTypeLaunchServerResult>; |  | ||||||
|   launchPersistentContext(params: BrowserTypeLaunchPersistentContextParams): Promise<BrowserTypeLaunchPersistentContextResult>; |   launchPersistentContext(params: BrowserTypeLaunchPersistentContextParams): Promise<BrowserTypeLaunchPersistentContextResult>; | ||||||
| } | } | ||||||
| export type BrowserTypeConnectParams = { |  | ||||||
|   wsEndpoint: string, |  | ||||||
|   slowMo?: number, |  | ||||||
|   timeout?: number, |  | ||||||
| }; |  | ||||||
| export type BrowserTypeConnectOptions = { |  | ||||||
|   slowMo?: number, |  | ||||||
|   timeout?: number, |  | ||||||
| }; |  | ||||||
| export type BrowserTypeConnectResult = { |  | ||||||
|   browser: BrowserChannel, |  | ||||||
| }; |  | ||||||
| export type BrowserTypeLaunchParams = { | export type BrowserTypeLaunchParams = { | ||||||
|   executablePath?: string, |   executablePath?: string, | ||||||
|   args?: string[], |   args?: string[], | ||||||
| @ -213,61 +199,6 @@ export type BrowserTypeLaunchOptions = { | |||||||
| export type BrowserTypeLaunchResult = { | export type BrowserTypeLaunchResult = { | ||||||
|   browser: BrowserChannel, |   browser: BrowserChannel, | ||||||
| }; | }; | ||||||
| export type BrowserTypeLaunchServerParams = { |  | ||||||
|   executablePath?: string, |  | ||||||
|   args?: string[], |  | ||||||
|   ignoreAllDefaultArgs?: boolean, |  | ||||||
|   ignoreDefaultArgs?: string[], |  | ||||||
|   handleSIGINT?: boolean, |  | ||||||
|   handleSIGTERM?: boolean, |  | ||||||
|   handleSIGHUP?: boolean, |  | ||||||
|   timeout?: number, |  | ||||||
|   env?: { |  | ||||||
|     name: string, |  | ||||||
|     value: string, |  | ||||||
|   }[], |  | ||||||
|   headless?: boolean, |  | ||||||
|   devtools?: boolean, |  | ||||||
|   proxy?: { |  | ||||||
|     server: string, |  | ||||||
|     bypass?: string, |  | ||||||
|     username?: string, |  | ||||||
|     password?: string, |  | ||||||
|   }, |  | ||||||
|   downloadsPath?: string, |  | ||||||
|   firefoxUserPrefs?: any, |  | ||||||
|   chromiumSandbox?: boolean, |  | ||||||
|   port?: number, |  | ||||||
| }; |  | ||||||
| export type BrowserTypeLaunchServerOptions = { |  | ||||||
|   executablePath?: string, |  | ||||||
|   args?: string[], |  | ||||||
|   ignoreAllDefaultArgs?: boolean, |  | ||||||
|   ignoreDefaultArgs?: string[], |  | ||||||
|   handleSIGINT?: boolean, |  | ||||||
|   handleSIGTERM?: boolean, |  | ||||||
|   handleSIGHUP?: boolean, |  | ||||||
|   timeout?: number, |  | ||||||
|   env?: { |  | ||||||
|     name: string, |  | ||||||
|     value: string, |  | ||||||
|   }[], |  | ||||||
|   headless?: boolean, |  | ||||||
|   devtools?: boolean, |  | ||||||
|   proxy?: { |  | ||||||
|     server: string, |  | ||||||
|     bypass?: string, |  | ||||||
|     username?: string, |  | ||||||
|     password?: string, |  | ||||||
|   }, |  | ||||||
|   downloadsPath?: string, |  | ||||||
|   firefoxUserPrefs?: any, |  | ||||||
|   chromiumSandbox?: boolean, |  | ||||||
|   port?: number, |  | ||||||
| }; |  | ||||||
| export type BrowserTypeLaunchServerResult = { |  | ||||||
|   server: BrowserServerChannel, |  | ||||||
| }; |  | ||||||
| export type BrowserTypeLaunchPersistentContextParams = { | export type BrowserTypeLaunchPersistentContextParams = { | ||||||
|   userDataDir: string, |   userDataDir: string, | ||||||
|   executablePath?: string, |   executablePath?: string, | ||||||
| @ -385,27 +316,6 @@ export type BrowserTypeLaunchPersistentContextResult = { | |||||||
|   context: BrowserContextChannel, |   context: BrowserContextChannel, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| // ----------- BrowserServer -----------
 |  | ||||||
| export type BrowserServerInitializer = { |  | ||||||
|   wsEndpoint: string, |  | ||||||
|   pid: number, |  | ||||||
| }; |  | ||||||
| export interface BrowserServerChannel extends Channel { |  | ||||||
|   on(event: 'close', callback: (params: BrowserServerCloseEvent) => void): this; |  | ||||||
|   close(params?: BrowserServerCloseParams): Promise<BrowserServerCloseResult>; |  | ||||||
|   kill(params?: BrowserServerKillParams): Promise<BrowserServerKillResult>; |  | ||||||
| } |  | ||||||
| export type BrowserServerCloseEvent = { |  | ||||||
|   exitCode?: number, |  | ||||||
|   signal?: string, |  | ||||||
| }; |  | ||||||
| export type BrowserServerCloseParams = {}; |  | ||||||
| export type BrowserServerCloseOptions = {}; |  | ||||||
| export type BrowserServerCloseResult = void; |  | ||||||
| export type BrowserServerKillParams = {}; |  | ||||||
| export type BrowserServerKillOptions = {}; |  | ||||||
| export type BrowserServerKillResult = void; |  | ||||||
| 
 |  | ||||||
| // ----------- Browser -----------
 | // ----------- Browser -----------
 | ||||||
| export type BrowserInitializer = { | export type BrowserInitializer = { | ||||||
|   version: string, |   version: string, | ||||||
|  | |||||||
| @ -17,7 +17,7 @@ | |||||||
| export { Accessibility } from './accessibility'; | export { Accessibility } from './accessibility'; | ||||||
| export { Browser } from './browser'; | export { Browser } from './browser'; | ||||||
| export { BrowserContext } from './browserContext'; | export { BrowserContext } from './browserContext'; | ||||||
| export { BrowserServer } from './browserServer'; | export { BrowserServer } from './browserType'; | ||||||
| export { BrowserType } from './browserType'; | export { BrowserType } from './browserType'; | ||||||
| export { ConsoleMessage } from './consoleMessage'; | export { ConsoleMessage } from './consoleMessage'; | ||||||
| export { Dialog } from './dialog'; | export { Dialog } from './dialog'; | ||||||
|  | |||||||
| @ -41,11 +41,7 @@ export class Browser extends ChannelOwner<BrowserChannel, BrowserInitializer> { | |||||||
|   constructor(parent: ChannelOwner, type: string, guid: string, initializer: BrowserInitializer) { |   constructor(parent: ChannelOwner, type: string, guid: string, initializer: BrowserInitializer) { | ||||||
|     super(parent, type, guid, initializer); |     super(parent, type, guid, initializer); | ||||||
|     this._browserType = parent as BrowserType; |     this._browserType = parent as BrowserType; | ||||||
|     this._channel.on('close', () => { |     this._channel.on('close', () => this._didClose()); | ||||||
|       this._isConnected = false; |  | ||||||
|       this.emit(Events.Browser.Disconnected); |  | ||||||
|       this._isClosedOrClosing = true; |  | ||||||
|     }); |  | ||||||
|     this._closedPromise = new Promise(f => this.once(Events.Browser.Disconnected, f)); |     this._closedPromise = new Promise(f => this.once(Events.Browser.Disconnected, f)); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| @ -95,4 +91,10 @@ export class Browser extends ChannelOwner<BrowserChannel, BrowserInitializer> { | |||||||
|       await this._closedPromise; |       await this._closedPromise; | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   _didClose() { | ||||||
|  |     this._isConnected = false; | ||||||
|  |     this.emit(Events.Browser.Disconnected); | ||||||
|  |     this._isClosedOrClosing = true; | ||||||
|  |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -214,7 +214,7 @@ export class BrowserContext extends ChannelOwner<BrowserContextChannel, BrowserC | |||||||
|     return result; |     return result; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   private async _onClose() { |   async _onClose() { | ||||||
|     this._isClosedOrClosing = true; |     this._isClosedOrClosing = true; | ||||||
|     if (this._browser) |     if (this._browser) | ||||||
|       this._browser._contexts.delete(this); |       this._browser._contexts.delete(this); | ||||||
|  | |||||||
| @ -1,53 +0,0 @@ | |||||||
| /** |  | ||||||
|  * Copyright (c) Microsoft Corporation. |  | ||||||
|  * |  | ||||||
|  * 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 { ChildProcess } from 'child_process'; |  | ||||||
| import { BrowserServerChannel, BrowserServerInitializer } from '../channels'; |  | ||||||
| import { ChannelOwner } from './channelOwner'; |  | ||||||
| import { Events } from './events'; |  | ||||||
| 
 |  | ||||||
| export class BrowserServer extends ChannelOwner<BrowserServerChannel, BrowserServerInitializer> { |  | ||||||
|   static from(server: BrowserServerChannel): BrowserServer { |  | ||||||
|     return (server as any)._object; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   constructor(parent: ChannelOwner, type: string, guid: string, initializer: BrowserServerInitializer) { |  | ||||||
|     super(parent, type, guid, initializer); |  | ||||||
|     this._channel.on('close', ({ exitCode, signal }) => { |  | ||||||
|       this.emit(Events.BrowserServer.Close, exitCode === undefined ? null : exitCode, signal === undefined ? null : signal); |  | ||||||
|     }); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   process(): ChildProcess { |  | ||||||
|     return { pid: this._initializer.pid } as any; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   wsEndpoint(): string { |  | ||||||
|     return this._initializer.wsEndpoint; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   async kill(): Promise<void> { |  | ||||||
|     return this._wrapApiCall('browserServer.kill', async () => { |  | ||||||
|       await this._channel.kill(); |  | ||||||
|     }); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   async close(): Promise<void> { |  | ||||||
|     return this._wrapApiCall('browserServer.close', async () => { |  | ||||||
|       await this._channel.close(); |  | ||||||
|     }); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| @ -14,16 +14,34 @@ | |||||||
|  * limitations under the License. |  * limitations under the License. | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| import { BrowserTypeChannel, BrowserTypeInitializer, BrowserTypeLaunchParams, BrowserTypeLaunchServerParams, BrowserTypeLaunchPersistentContextParams } from '../channels'; | import { BrowserTypeChannel, BrowserTypeInitializer, BrowserTypeLaunchParams, BrowserTypeLaunchPersistentContextParams } from '../channels'; | ||||||
| import { Browser } from './browser'; | import { Browser } from './browser'; | ||||||
| import { BrowserContext } from './browserContext'; | import { BrowserContext } from './browserContext'; | ||||||
| import { ChannelOwner } from './channelOwner'; | import { ChannelOwner } from './channelOwner'; | ||||||
| import { BrowserServer } from './browserServer'; |  | ||||||
| import { headersObjectToArray, envObjectToArray } from '../../converters'; | import { headersObjectToArray, envObjectToArray } from '../../converters'; | ||||||
| import { assert } from '../../helper'; | import { assert, helper } from '../../helper'; | ||||||
| import { LaunchOptions, LaunchServerOptions, ConnectOptions, LaunchPersistentContextOptions } from './types'; | import { LaunchOptions, LaunchServerOptions, ConnectOptions, LaunchPersistentContextOptions } from './types'; | ||||||
|  | import * as WebSocket from 'ws'; | ||||||
|  | import { Connection } from './connection'; | ||||||
|  | import { serializeError } from '../serializers'; | ||||||
|  | import { Events } from './events'; | ||||||
|  | import { TimeoutSettings } from '../../timeoutSettings'; | ||||||
|  | import { ChildProcess } from 'child_process'; | ||||||
|  | 
 | ||||||
|  | export interface BrowserServerLauncher { | ||||||
|  |   launchServer(options?: LaunchServerOptions): Promise<BrowserServer>; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export interface BrowserServer { | ||||||
|  |   process(): ChildProcess; | ||||||
|  |   wsEndpoint(): string; | ||||||
|  |   close(): Promise<void>; | ||||||
|  |   kill(): Promise<void>; | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| export class BrowserType extends ChannelOwner<BrowserTypeChannel, BrowserTypeInitializer> { | export class BrowserType extends ChannelOwner<BrowserTypeChannel, BrowserTypeInitializer> { | ||||||
|  |   private _timeoutSettings = new TimeoutSettings(); | ||||||
|  |   _serverLauncher?: BrowserServerLauncher; | ||||||
| 
 | 
 | ||||||
|   static from(browserType: BrowserTypeChannel): BrowserType { |   static from(browserType: BrowserTypeChannel): BrowserType { | ||||||
|     return (browserType as any)._object; |     return (browserType as any)._object; | ||||||
| @ -60,17 +78,9 @@ export class BrowserType extends ChannelOwner<BrowserTypeChannel, BrowserTypeIni | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async launchServer(options: LaunchServerOptions = {}): Promise<BrowserServer> { |   async launchServer(options: LaunchServerOptions = {}): Promise<BrowserServer> { | ||||||
|     const logger = options.logger; |     if (!this._serverLauncher) | ||||||
|     options = { ...options, logger: undefined }; |       throw new Error('Launching server is not supported'); | ||||||
|     return this._wrapApiCall('browserType.launchServer', async () => { |     return this._serverLauncher.launchServer(options); | ||||||
|       const launchServerOptions: BrowserTypeLaunchServerParams = { |  | ||||||
|         ...options, |  | ||||||
|         ignoreDefaultArgs: Array.isArray(options.ignoreDefaultArgs) ? options.ignoreDefaultArgs : undefined, |  | ||||||
|         ignoreAllDefaultArgs: !!options.ignoreDefaultArgs && !Array.isArray(options.ignoreDefaultArgs), |  | ||||||
|         env: options.env ? envObjectToArray(options.env) : undefined, |  | ||||||
|       }; |  | ||||||
|       return BrowserServer.from((await this._channel.launchServer(launchServerOptions)).server); |  | ||||||
|     }, logger); |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async launchPersistentContext(userDataDir: string, options: LaunchPersistentContextOptions = {}): Promise<BrowserContext> { |   async launchPersistentContext(userDataDir: string, options: LaunchPersistentContextOptions = {}): Promise<BrowserContext> { | ||||||
| @ -96,11 +106,64 @@ export class BrowserType extends ChannelOwner<BrowserTypeChannel, BrowserTypeIni | |||||||
| 
 | 
 | ||||||
|   async connect(options: ConnectOptions): Promise<Browser> { |   async connect(options: ConnectOptions): Promise<Browser> { | ||||||
|     const logger = options.logger; |     const logger = options.logger; | ||||||
|     options = { ...options, logger: undefined }; |  | ||||||
|     return this._wrapApiCall('browserType.connect', async () => { |     return this._wrapApiCall('browserType.connect', async () => { | ||||||
|       const browser = Browser.from((await this._channel.connect(options)).browser); |       const connection = new Connection(); | ||||||
|       browser._logger = logger; | 
 | ||||||
|       return browser; |       const ws = new WebSocket(options.wsEndpoint, [], { | ||||||
|  |         perMessageDeflate: false, | ||||||
|  |         maxPayload: 256 * 1024 * 1024, // 256Mb,
 | ||||||
|  |         handshakeTimeout: this._timeoutSettings.timeout(options), | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |       // The 'ws' module in node sometimes sends us multiple messages in a single task.
 | ||||||
|  |       const waitForNextTask = options.slowMo | ||||||
|  |         ? (cb: () => any) => setTimeout(cb, options.slowMo) | ||||||
|  |         : helper.makeWaitForNextTask(); | ||||||
|  |       connection.onmessage = message => { | ||||||
|  |         if (ws.readyState !== WebSocket.OPEN) { | ||||||
|  |           setTimeout(() => { | ||||||
|  |             connection.dispatch({ id: (message as any).id, error: serializeError(new Error('Browser has been closed')) }); | ||||||
|  |           }, 0); | ||||||
|  |           return; | ||||||
|  |         } | ||||||
|  |         ws.send(JSON.stringify(message)); | ||||||
|  |       }; | ||||||
|  |       ws.addEventListener('message', event => { | ||||||
|  |         waitForNextTask(() => connection.dispatch(JSON.parse(event.data))); | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |       return await new Promise<Browser>(async (fulfill, reject) => { | ||||||
|  |         if ((options as any).__testHookBeforeCreateBrowser) { | ||||||
|  |           try { | ||||||
|  |             await (options as any).__testHookBeforeCreateBrowser(); | ||||||
|  |           } catch (e) { | ||||||
|  |             reject(e); | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |         ws.addEventListener('open', async () => { | ||||||
|  |           const browser = (await connection.waitForObjectWithKnownName('connectedBrowser')) as Browser; | ||||||
|  |           browser._logger = logger; | ||||||
|  |           const closeListener = () => { | ||||||
|  |             // Emulate all pages, contexts and the browser closing upon disconnect.
 | ||||||
|  |             for (const context of browser.contexts()) { | ||||||
|  |               for (const page of context.pages()) | ||||||
|  |                 page._onClose(); | ||||||
|  |               context._onClose(); | ||||||
|  |             } | ||||||
|  |             browser._didClose(); | ||||||
|  |           }; | ||||||
|  |           ws.addEventListener('close', closeListener); | ||||||
|  |           browser.on(Events.Browser.Disconnected, () => { | ||||||
|  |             ws.removeEventListener('close', closeListener); | ||||||
|  |             ws.close(); | ||||||
|  |           }); | ||||||
|  |           fulfill(browser); | ||||||
|  |         }); | ||||||
|  |         ws.addEventListener('error', event => { | ||||||
|  |           ws.close(); | ||||||
|  |           reject(new Error('WebSocket error: ' + event.message)); | ||||||
|  |         }); | ||||||
|  |       }); | ||||||
|     }, logger); |     }, logger); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -29,7 +29,6 @@ import { ConsoleMessage } from './consoleMessage'; | |||||||
| import { Dialog } from './dialog'; | import { Dialog } from './dialog'; | ||||||
| import { Download } from './download'; | import { Download } from './download'; | ||||||
| import { parseError } from '../serializers'; | import { parseError } from '../serializers'; | ||||||
| import { BrowserServer } from './browserServer'; |  | ||||||
| import { CDPSession } from './cdpSession'; | import { CDPSession } from './cdpSession'; | ||||||
| import { Playwright } from './playwright'; | import { Playwright } from './playwright'; | ||||||
| import { Electron, ElectronApplication } from './electron'; | import { Electron, ElectronApplication } from './electron'; | ||||||
| @ -160,9 +159,6 @@ export class Connection { | |||||||
|         else |         else | ||||||
|           result = new BrowserContext(parent, type, guid, initializer, browserName); |           result = new BrowserContext(parent, type, guid, initializer, browserName); | ||||||
|         break; |         break; | ||||||
|       case 'BrowserServer': |  | ||||||
|         result = new BrowserServer(parent, type, guid, initializer); |  | ||||||
|         break; |  | ||||||
|       case 'BrowserType': |       case 'BrowserType': | ||||||
|         result = new BrowserType(parent, type, guid, initializer); |         result = new BrowserType(parent, type, guid, initializer); | ||||||
|         break; |         break; | ||||||
|  | |||||||
| @ -173,7 +173,7 @@ export class Page extends ChannelOwner<PageChannel, PageInitializer> { | |||||||
|     this.emit(Events.Page.Worker, worker); |     this.emit(Events.Page.Worker, worker); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   private _onClose() { |   _onClose() { | ||||||
|     this._closed = true; |     this._closed = true; | ||||||
|     this._browserContext._pages.delete(this); |     this._browserContext._pages.delete(this); | ||||||
|     this.emit(Events.Page.Close); |     this.emit(Events.Page.Close); | ||||||
|  | |||||||
| @ -15,13 +15,15 @@ | |||||||
|  * limitations under the License. |  * limitations under the License. | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| import { BrowserNewContextOptions, BrowserTypeLaunchOptions, BrowserTypeLaunchServerOptions, BrowserTypeConnectParams } from '../channels'; | import { BrowserNewContextOptions, BrowserTypeLaunchOptions } from '../channels'; | ||||||
| 
 | 
 | ||||||
| type LoggerSeverity = 'verbose' | 'info' | 'warning' | 'error'; | type LoggerSeverity = 'verbose' | 'info' | 'warning' | 'error'; | ||||||
| export interface LoggerSink { | export interface LoggerSink { | ||||||
|   isEnabled(name: string, severity: LoggerSeverity): boolean; |   isEnabled(name: string, severity: LoggerSeverity): boolean; | ||||||
|   log(name: string, severity: LoggerSeverity, message: string | Error, args: any[], hints: { color?: string }): void; |   log(name: string, severity: LoggerSeverity, message: string | Error, args: any[], hints: { color?: string }): void; | ||||||
| } | } | ||||||
|  | // This is a workaround for the documentation generation.
 | ||||||
|  | export interface Logger extends LoggerSink {} | ||||||
| 
 | 
 | ||||||
| export type Size = { width: number, height: number }; | export type Size = { width: number, height: number }; | ||||||
| export type Point = { x: number, y: number }; | export type Point = { x: number, y: number }; | ||||||
| @ -56,6 +58,33 @@ type FirefoxUserPrefs = { | |||||||
| }; | }; | ||||||
| type LaunchOptionsBase = Omit<BrowserTypeLaunchOptions, 'ignoreAllDefaultArgs' | 'ignoreDefaultArgs' | 'env' | 'firefoxUserPrefs'> & LaunchOverrides; | type LaunchOptionsBase = Omit<BrowserTypeLaunchOptions, 'ignoreAllDefaultArgs' | 'ignoreDefaultArgs' | 'env' | 'firefoxUserPrefs'> & LaunchOverrides; | ||||||
| export type LaunchOptions = LaunchOptionsBase & FirefoxUserPrefs; | export type LaunchOptions = LaunchOptionsBase & FirefoxUserPrefs; | ||||||
| export type LaunchServerOptions = Omit<BrowserTypeLaunchServerOptions, 'ignoreAllDefaultArgs' | 'ignoreDefaultArgs' | 'env' | 'firefoxUserPrefs'> & LaunchOverrides & FirefoxUserPrefs; |  | ||||||
| export type LaunchPersistentContextOptions = LaunchOptionsBase & BrowserContextOptions; | export type LaunchPersistentContextOptions = LaunchOptionsBase & BrowserContextOptions; | ||||||
| export type ConnectOptions = BrowserTypeConnectParams & { logger?: LoggerSink }; | 
 | ||||||
|  | export type ConnectOptions = { | ||||||
|  |   wsEndpoint: string, | ||||||
|  |   slowMo?: number, | ||||||
|  |   timeout?: number, | ||||||
|  |   logger?: LoggerSink, | ||||||
|  | }; | ||||||
|  | export type LaunchServerOptions = { | ||||||
|  |   executablePath?: string, | ||||||
|  |   args?: string[], | ||||||
|  |   ignoreDefaultArgs?: boolean | string[], | ||||||
|  |   handleSIGINT?: boolean, | ||||||
|  |   handleSIGTERM?: boolean, | ||||||
|  |   handleSIGHUP?: boolean, | ||||||
|  |   timeout?: number, | ||||||
|  |   env?: Env, | ||||||
|  |   headless?: boolean, | ||||||
|  |   devtools?: boolean, | ||||||
|  |   proxy?: { | ||||||
|  |     server: string, | ||||||
|  |     bypass?: string, | ||||||
|  |     username?: string, | ||||||
|  |     password?: string | ||||||
|  |   }, | ||||||
|  |   downloadsPath?: string, | ||||||
|  |   chromiumSandbox?: boolean, | ||||||
|  |   port?: number, | ||||||
|  |   logger?: LoggerSink, | ||||||
|  | } & FirefoxUserPrefs; | ||||||
|  | |||||||
| @ -21,6 +21,7 @@ import { PlaywrightDispatcher } from './server/playwrightDispatcher'; | |||||||
| import { setUseApiName } from '../progress'; | import { setUseApiName } from '../progress'; | ||||||
| import { Connection } from './client/connection'; | import { Connection } from './client/connection'; | ||||||
| import { isUnderTest } from '../helper'; | import { isUnderTest } from '../helper'; | ||||||
|  | import { BrowserServerLauncherImpl } from './browserServerImpl'; | ||||||
| 
 | 
 | ||||||
| export function setupInProcess(playwright: PlaywrightImpl): PlaywrightAPI { | export function setupInProcess(playwright: PlaywrightImpl): PlaywrightAPI { | ||||||
|   setUseApiName(false); |   setUseApiName(false); | ||||||
| @ -34,13 +35,16 @@ export function setupInProcess(playwright: PlaywrightImpl): PlaywrightAPI { | |||||||
| 
 | 
 | ||||||
|   // Initialize Playwright channel.
 |   // Initialize Playwright channel.
 | ||||||
|   new PlaywrightDispatcher(dispatcherConnection.rootDispatcher(), playwright); |   new PlaywrightDispatcher(dispatcherConnection.rootDispatcher(), playwright); | ||||||
|   const playwrightAPI = clientConnection.getObjectWithKnownName('Playwright'); |   const playwrightAPI = clientConnection.getObjectWithKnownName('Playwright') as PlaywrightAPI; | ||||||
|  |   playwrightAPI.chromium._serverLauncher = new BrowserServerLauncherImpl(playwright.chromium); | ||||||
|  |   playwrightAPI.firefox._serverLauncher = new BrowserServerLauncherImpl(playwright.firefox); | ||||||
|  |   playwrightAPI.webkit._serverLauncher = new BrowserServerLauncherImpl(playwright.webkit); | ||||||
| 
 | 
 | ||||||
|   // Switch to async dispatch after we got Playwright object.
 |   // Switch to async dispatch after we got Playwright object.
 | ||||||
|   dispatcherConnection.onmessage = message => setImmediate(() => clientConnection.dispatch(message)); |   dispatcherConnection.onmessage = message => setImmediate(() => clientConnection.dispatch(message)); | ||||||
|   clientConnection.onmessage = message => setImmediate(() => dispatcherConnection.dispatch(message)); |   clientConnection.onmessage = message => setImmediate(() => dispatcherConnection.dispatch(message)); | ||||||
| 
 | 
 | ||||||
|   if (isUnderTest()) |   if (isUnderTest()) | ||||||
|     playwrightAPI._toImpl = (x: any) => dispatcherConnection._dispatchers.get(x._guid)!._object; |     (playwrightAPI as any)._toImpl = (x: any) => dispatcherConnection._dispatchers.get(x._guid)!._object; | ||||||
|   return playwrightAPI; |   return playwrightAPI; | ||||||
| } | } | ||||||
|  | |||||||
| @ -176,14 +176,6 @@ BrowserType: | |||||||
| 
 | 
 | ||||||
|   commands: |   commands: | ||||||
| 
 | 
 | ||||||
|     connect: |  | ||||||
|       parameters: |  | ||||||
|         wsEndpoint: string |  | ||||||
|         slowMo: number? |  | ||||||
|         timeout: number? |  | ||||||
|       returns: |  | ||||||
|         browser: Browser |  | ||||||
| 
 |  | ||||||
|     launch: |     launch: | ||||||
|       parameters: |       parameters: | ||||||
|         executablePath: string? |         executablePath: string? | ||||||
| @ -221,43 +213,6 @@ BrowserType: | |||||||
|       returns: |       returns: | ||||||
|         browser: Browser |         browser: Browser | ||||||
| 
 | 
 | ||||||
|     launchServer: |  | ||||||
|       parameters: |  | ||||||
|         executablePath: string? |  | ||||||
|         args: |  | ||||||
|           type: array? |  | ||||||
|           items: string |  | ||||||
|         ignoreAllDefaultArgs: boolean? |  | ||||||
|         ignoreDefaultArgs: |  | ||||||
|           type: array? |  | ||||||
|           items: string |  | ||||||
|         handleSIGINT: boolean? |  | ||||||
|         handleSIGTERM: boolean? |  | ||||||
|         handleSIGHUP: boolean? |  | ||||||
|         timeout: number? |  | ||||||
|         env: |  | ||||||
|           type: array? |  | ||||||
|           items: |  | ||||||
|             type: object |  | ||||||
|             properties: |  | ||||||
|               name: string |  | ||||||
|               value: string |  | ||||||
|         headless: boolean? |  | ||||||
|         devtools: boolean? |  | ||||||
|         proxy: |  | ||||||
|           type: object? |  | ||||||
|           properties: |  | ||||||
|             server: string |  | ||||||
|             bypass: string? |  | ||||||
|             username: string? |  | ||||||
|             password: string? |  | ||||||
|         downloadsPath: string? |  | ||||||
|         firefoxUserPrefs: json? |  | ||||||
|         chromiumSandbox: boolean? |  | ||||||
|         port: number? |  | ||||||
|       returns: |  | ||||||
|         server: BrowserServer |  | ||||||
| 
 |  | ||||||
|     launchPersistentContext: |     launchPersistentContext: | ||||||
|       parameters: |       parameters: | ||||||
|         userDataDir: string |         userDataDir: string | ||||||
| @ -340,28 +295,6 @@ BrowserType: | |||||||
|         context: BrowserContext |         context: BrowserContext | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| BrowserServer: |  | ||||||
|   type: interface |  | ||||||
| 
 |  | ||||||
|   initializer: |  | ||||||
|     wsEndpoint: string |  | ||||||
|     pid: number |  | ||||||
| 
 |  | ||||||
|   commands: |  | ||||||
| 
 |  | ||||||
|     close: |  | ||||||
| 
 |  | ||||||
|     kill: |  | ||||||
| 
 |  | ||||||
|   events: |  | ||||||
| 
 |  | ||||||
|     close: |  | ||||||
|       parameters: |  | ||||||
|         exitCode: number? |  | ||||||
|         signal: string? |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| Browser: | Browser: | ||||||
|   type: interface |   type: interface | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -26,12 +26,14 @@ import { PageDispatcher } from './pageDispatcher'; | |||||||
| import { headersArrayToObject } from '../../converters'; | import { headersArrayToObject } from '../../converters'; | ||||||
| 
 | 
 | ||||||
| export class BrowserDispatcher extends Dispatcher<Browser, BrowserInitializer> implements BrowserChannel { | export class BrowserDispatcher extends Dispatcher<Browser, BrowserInitializer> implements BrowserChannel { | ||||||
|   constructor(scope: DispatcherScope, browser: BrowserBase) { |   constructor(scope: DispatcherScope, browser: BrowserBase, guid?: string) { | ||||||
|     super(scope, browser, 'Browser', { version: browser.version() }, true); |     super(scope, browser, 'Browser', { version: browser.version() }, true, guid); | ||||||
|     browser.on(Events.Browser.Disconnected, () => { |     browser.on(Events.Browser.Disconnected, () => this._didClose()); | ||||||
|       this._dispatchEvent('close'); |   } | ||||||
|       this._dispose(); | 
 | ||||||
|     }); |   _didClose() { | ||||||
|  |     this._dispatchEvent('close'); | ||||||
|  |     this._dispose(); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async newContext(params: BrowserNewContextParams): Promise<{ context: BrowserContextChannel }> { |   async newContext(params: BrowserNewContextParams): Promise<{ context: BrowserContextChannel }> { | ||||||
|  | |||||||
| @ -1,44 +0,0 @@ | |||||||
| /** |  | ||||||
|  * Copyright (c) Microsoft Corporation. |  | ||||||
|  * |  | ||||||
|  * 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 { BrowserServer } from '../../server/browserServer'; |  | ||||||
| import { BrowserServerChannel, BrowserServerInitializer } from '../channels'; |  | ||||||
| import { Dispatcher, DispatcherScope } from './dispatcher'; |  | ||||||
| import { Events } from '../../events'; |  | ||||||
| 
 |  | ||||||
| export class BrowserServerDispatcher extends Dispatcher<BrowserServer, BrowserServerInitializer> implements BrowserServerChannel { |  | ||||||
|   constructor(scope: DispatcherScope, browserServer: BrowserServer) { |  | ||||||
|     super(scope, browserServer, 'BrowserServer', { |  | ||||||
|       wsEndpoint: browserServer.wsEndpoint(), |  | ||||||
|       pid: browserServer.process().pid |  | ||||||
|     }, true); |  | ||||||
|     browserServer.on(Events.BrowserServer.Close, (exitCode, signal) => { |  | ||||||
|       this._dispatchEvent('close', { |  | ||||||
|         exitCode: exitCode === null ? undefined : exitCode, |  | ||||||
|         signal: signal === null ? undefined : signal, |  | ||||||
|       }); |  | ||||||
|       this._dispose(); |  | ||||||
|     }); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   async close(): Promise<void> { |  | ||||||
|     await this._object.close(); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   async kill(): Promise<void> { |  | ||||||
|     await this._object.close(); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| @ -16,13 +16,11 @@ | |||||||
| 
 | 
 | ||||||
| import { BrowserBase } from '../../browser'; | import { BrowserBase } from '../../browser'; | ||||||
| import { BrowserTypeBase, BrowserType } from '../../server/browserType'; | import { BrowserTypeBase, BrowserType } from '../../server/browserType'; | ||||||
| import * as types from '../../types'; |  | ||||||
| import { BrowserDispatcher } from './browserDispatcher'; | import { BrowserDispatcher } from './browserDispatcher'; | ||||||
| import { BrowserChannel, BrowserTypeChannel, BrowserContextChannel, BrowserTypeInitializer, BrowserServerChannel, BrowserTypeLaunchParams, BrowserTypeLaunchPersistentContextParams, BrowserTypeLaunchServerParams } from '../channels'; | import { BrowserChannel, BrowserTypeChannel, BrowserContextChannel, BrowserTypeInitializer, BrowserTypeLaunchParams, BrowserTypeLaunchPersistentContextParams } from '../channels'; | ||||||
| import { Dispatcher, DispatcherScope } from './dispatcher'; | import { Dispatcher, DispatcherScope } from './dispatcher'; | ||||||
| import { BrowserContextBase } from '../../browserContext'; | import { BrowserContextBase } from '../../browserContext'; | ||||||
| import { BrowserContextDispatcher } from './browserContextDispatcher'; | import { BrowserContextDispatcher } from './browserContextDispatcher'; | ||||||
| import { BrowserServerDispatcher } from './browserServerDispatcher'; |  | ||||||
| import { headersArrayToObject, envArrayToObject } from '../../converters'; | import { headersArrayToObject, envArrayToObject } from '../../converters'; | ||||||
| 
 | 
 | ||||||
| export class BrowserTypeDispatcher extends Dispatcher<BrowserType, BrowserTypeInitializer> implements BrowserTypeChannel { | export class BrowserTypeDispatcher extends Dispatcher<BrowserType, BrowserTypeInitializer> implements BrowserTypeChannel { | ||||||
| @ -54,18 +52,4 @@ export class BrowserTypeDispatcher extends Dispatcher<BrowserType, BrowserTypeIn | |||||||
|     const browserContext = await this._object.launchPersistentContext(params.userDataDir, options); |     const browserContext = await this._object.launchPersistentContext(params.userDataDir, options); | ||||||
|     return { context: new BrowserContextDispatcher(this._scope, browserContext as BrowserContextBase) }; |     return { context: new BrowserContextDispatcher(this._scope, browserContext as BrowserContextBase) }; | ||||||
|   } |   } | ||||||
| 
 |  | ||||||
|   async launchServer(params: BrowserTypeLaunchServerParams): Promise<{ server: BrowserServerChannel }> { |  | ||||||
|     const options = { |  | ||||||
|       ...params, |  | ||||||
|       ignoreDefaultArgs: params.ignoreAllDefaultArgs ? true : params.ignoreDefaultArgs, |  | ||||||
|       env: params.env ? envArrayToObject(params.env) : undefined, |  | ||||||
|     }; |  | ||||||
|     return { server: new BrowserServerDispatcher(this._scope, await this._object.launchServer(options)) }; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   async connect(params: types.ConnectOptions): Promise<{ browser: BrowserChannel }> { |  | ||||||
|     const browser = await this._object.connect(params); |  | ||||||
|     return { browser: new BrowserDispatcher(this._scope, browser as BrowserBase) }; |  | ||||||
|   } |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -100,11 +100,6 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme { | |||||||
|     name: tString, |     name: tString, | ||||||
|     handle: tChannel('ElementHandle'), |     handle: tChannel('ElementHandle'), | ||||||
|   }); |   }); | ||||||
|   scheme.BrowserTypeConnectParams = tObject({ |  | ||||||
|     wsEndpoint: tString, |  | ||||||
|     slowMo: tOptional(tNumber), |  | ||||||
|     timeout: tOptional(tNumber), |  | ||||||
|   }); |  | ||||||
|   scheme.BrowserTypeLaunchParams = tObject({ |   scheme.BrowserTypeLaunchParams = tObject({ | ||||||
|     executablePath: tOptional(tString), |     executablePath: tOptional(tString), | ||||||
|     args: tOptional(tArray(tString)), |     args: tOptional(tArray(tString)), | ||||||
| @ -131,32 +126,6 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme { | |||||||
|     chromiumSandbox: tOptional(tBoolean), |     chromiumSandbox: tOptional(tBoolean), | ||||||
|     slowMo: tOptional(tNumber), |     slowMo: tOptional(tNumber), | ||||||
|   }); |   }); | ||||||
|   scheme.BrowserTypeLaunchServerParams = tObject({ |  | ||||||
|     executablePath: tOptional(tString), |  | ||||||
|     args: tOptional(tArray(tString)), |  | ||||||
|     ignoreAllDefaultArgs: tOptional(tBoolean), |  | ||||||
|     ignoreDefaultArgs: tOptional(tArray(tString)), |  | ||||||
|     handleSIGINT: tOptional(tBoolean), |  | ||||||
|     handleSIGTERM: tOptional(tBoolean), |  | ||||||
|     handleSIGHUP: tOptional(tBoolean), |  | ||||||
|     timeout: tOptional(tNumber), |  | ||||||
|     env: tOptional(tArray(tObject({ |  | ||||||
|       name: tString, |  | ||||||
|       value: tString, |  | ||||||
|     }))), |  | ||||||
|     headless: tOptional(tBoolean), |  | ||||||
|     devtools: tOptional(tBoolean), |  | ||||||
|     proxy: tOptional(tObject({ |  | ||||||
|       server: tString, |  | ||||||
|       bypass: tOptional(tString), |  | ||||||
|       username: tOptional(tString), |  | ||||||
|       password: tOptional(tString), |  | ||||||
|     })), |  | ||||||
|     downloadsPath: tOptional(tString), |  | ||||||
|     firefoxUserPrefs: tOptional(tAny), |  | ||||||
|     chromiumSandbox: tOptional(tBoolean), |  | ||||||
|     port: tOptional(tNumber), |  | ||||||
|   }); |  | ||||||
|   scheme.BrowserTypeLaunchPersistentContextParams = tObject({ |   scheme.BrowserTypeLaunchPersistentContextParams = tObject({ | ||||||
|     userDataDir: tString, |     userDataDir: tString, | ||||||
|     executablePath: tOptional(tString), |     executablePath: tOptional(tString), | ||||||
| @ -214,8 +183,6 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme { | |||||||
|     colorScheme: tOptional(tEnum(['light', 'dark', 'no-preference'])), |     colorScheme: tOptional(tEnum(['light', 'dark', 'no-preference'])), | ||||||
|     acceptDownloads: tOptional(tBoolean), |     acceptDownloads: tOptional(tBoolean), | ||||||
|   }); |   }); | ||||||
|   scheme.BrowserServerCloseParams = tOptional(tObject({})); |  | ||||||
|   scheme.BrowserServerKillParams = tOptional(tObject({})); |  | ||||||
|   scheme.BrowserCloseParams = tOptional(tObject({})); |   scheme.BrowserCloseParams = tOptional(tObject({})); | ||||||
|   scheme.BrowserNewContextParams = tObject({ |   scheme.BrowserNewContextParams = tObject({ | ||||||
|     noDefaultViewport: tOptional(tBoolean), |     noDefaultViewport: tOptional(tBoolean), | ||||||
|  | |||||||
| @ -19,7 +19,7 @@ import './base.fixture'; | |||||||
| import utils from './utils'; | import utils from './utils'; | ||||||
| const {FFOX, CHROMIUM, WEBKIT, WIN, USES_HOOKS, CHANNEL} = testOptions; | const {FFOX, CHROMIUM, WEBKIT, WIN, USES_HOOKS, CHANNEL} = testOptions; | ||||||
| 
 | 
 | ||||||
| it.slow()('should be able to reconnect to a browser', async({browserType, defaultBrowserOptions, server, toImpl}) => { | it.skip(USES_HOOKS).slow()('should be able to reconnect to a browser', async({browserType, defaultBrowserOptions, server}) => { | ||||||
|   const browserServer = await browserType.launchServer(defaultBrowserOptions); |   const browserServer = await browserType.launchServer(defaultBrowserOptions); | ||||||
|   { |   { | ||||||
|     const browser = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() }); |     const browser = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() }); | ||||||
| @ -35,33 +35,27 @@ it.slow()('should be able to reconnect to a browser', async({browserType, defaul | |||||||
|     await page.goto(server.EMPTY_PAGE); |     await page.goto(server.EMPTY_PAGE); | ||||||
|     await browser.close(); |     await browser.close(); | ||||||
|   } |   } | ||||||
|   if (toImpl) |  | ||||||
|     await toImpl(browserServer)._checkLeaks(); |  | ||||||
|   await browserServer.close(); |   await browserServer.close(); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| it.fail(USES_HOOKS || (CHROMIUM && WIN)).slow()('should handle exceptions during connect', async({browserType, defaultBrowserOptions, toImpl}) => { | it.skip(USES_HOOKS).fail(CHROMIUM && WIN).slow()('should handle exceptions during connect', async({browserType, defaultBrowserOptions}) => { | ||||||
|   const browserServer = await browserType.launchServer(defaultBrowserOptions); |   const browserServer = await browserType.launchServer(defaultBrowserOptions); | ||||||
|   const __testHookBeforeCreateBrowser = () => { throw new Error('Dummy') }; |   const __testHookBeforeCreateBrowser = () => { throw new Error('Dummy') }; | ||||||
|   const error = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint(), __testHookBeforeCreateBrowser } as any).catch(e => e); |   const error = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint(), __testHookBeforeCreateBrowser } as any).catch(e => e); | ||||||
|   if (toImpl) |  | ||||||
|     await toImpl(browserServer)._checkLeaks(); |  | ||||||
|   await browserServer.close(); |   await browserServer.close(); | ||||||
|   expect(error.message).toContain('Dummy'); |   expect(error.message).toContain('Dummy'); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| it('should set the browser connected state', async ({browserType, defaultBrowserOptions, toImpl}) => { | it.skip(USES_HOOKS)('should set the browser connected state', async ({browserType, defaultBrowserOptions}) => { | ||||||
|   const browserServer = await browserType.launchServer(defaultBrowserOptions); |   const browserServer = await browserType.launchServer(defaultBrowserOptions); | ||||||
|   const remote = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() }); |   const remote = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() }); | ||||||
|   expect(remote.isConnected()).toBe(true); |   expect(remote.isConnected()).toBe(true); | ||||||
|   await remote.close(); |   await remote.close(); | ||||||
|   expect(remote.isConnected()).toBe(false); |   expect(remote.isConnected()).toBe(false); | ||||||
|   if (toImpl) |  | ||||||
|     await toImpl(browserServer)._checkLeaks(); |  | ||||||
|   await browserServer.close(); |   await browserServer.close(); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| it('should throw when used after isConnected returns false', async({browserType, defaultBrowserOptions}) => { | it.skip(USES_HOOKS)('should throw when used after isConnected returns false', async({browserType, defaultBrowserOptions}) => { | ||||||
|   const browserServer = await browserType.launchServer(defaultBrowserOptions); |   const browserServer = await browserType.launchServer(defaultBrowserOptions); | ||||||
|   const remote = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() }); |   const remote = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() }); | ||||||
|   const page = await remote.newPage(); |   const page = await remote.newPage(); | ||||||
|  | |||||||
| @ -21,7 +21,7 @@ import fs from 'fs'; | |||||||
| import utils from './utils'; | import utils from './utils'; | ||||||
| const {FFOX, CHROMIUM, WEBKIT, WIN, USES_HOOKS, CHANNEL} = testOptions; | const {FFOX, CHROMIUM, WEBKIT, WIN, USES_HOOKS, CHANNEL} = testOptions; | ||||||
| 
 | 
 | ||||||
| it('should work', async({browserType, defaultBrowserOptions, toImpl}) => { | it.skip(USES_HOOKS)('should work', async({browserType, defaultBrowserOptions}) => { | ||||||
|   const browserServer = await browserType.launchServer(defaultBrowserOptions); |   const browserServer = await browserType.launchServer(defaultBrowserOptions); | ||||||
|   const browser = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() }); |   const browser = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() }); | ||||||
|   const browserContext = await browser.newContext(); |   const browserContext = await browser.newContext(); | ||||||
| @ -31,12 +31,10 @@ it('should work', async({browserType, defaultBrowserOptions, toImpl}) => { | |||||||
|   expect(await page.evaluate('11 * 11')).toBe(121); |   expect(await page.evaluate('11 * 11')).toBe(121); | ||||||
|   await page.close(); |   await page.close(); | ||||||
|   await browser.close(); |   await browser.close(); | ||||||
|   if (toImpl) |  | ||||||
|     await toImpl(browserServer)._checkLeaks(); |  | ||||||
|   await browserServer.close(); |   await browserServer.close(); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| it('should fire "disconnected" when closing the server', async({browserType, defaultBrowserOptions}) => { | it.skip(USES_HOOKS)('should fire "disconnected" when closing the server', async({browserType, defaultBrowserOptions}) => { | ||||||
|   const browserServer = await browserType.launchServer(defaultBrowserOptions); |   const browserServer = await browserType.launchServer(defaultBrowserOptions); | ||||||
|   const browser = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() }); |   const browser = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() }); | ||||||
|   const disconnectedEventPromise = new Promise(resolve => browser.once('disconnected', resolve)); |   const disconnectedEventPromise = new Promise(resolve => browser.once('disconnected', resolve)); | ||||||
| @ -48,7 +46,7 @@ it('should fire "disconnected" when closing the server', async({browserType, def | |||||||
|   ]); |   ]); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| it('should fire "close" event during kill', async({browserType, defaultBrowserOptions}) => { | it.skip(USES_HOOKS)('should fire "close" event during kill', async({browserType, defaultBrowserOptions}) => { | ||||||
|   const order = []; |   const order = []; | ||||||
|   const browserServer = await browserType.launchServer(defaultBrowserOptions); |   const browserServer = await browserType.launchServer(defaultBrowserOptions); | ||||||
|   const closedPromise = new Promise(f => browserServer.on('close', () => { |   const closedPromise = new Promise(f => browserServer.on('close', () => { | ||||||
| @ -62,13 +60,13 @@ it('should fire "close" event during kill', async({browserType, defaultBrowserOp | |||||||
|   expect(order).toEqual(['closed', 'killed']); |   expect(order).toEqual(['closed', 'killed']); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| it('should return child_process instance', async ({browserType, defaultBrowserOptions}) => { | it.skip(USES_HOOKS)('should return child_process instance', async ({browserType, defaultBrowserOptions}) => { | ||||||
|   const browserServer = await browserType.launchServer(defaultBrowserOptions); |   const browserServer = await browserType.launchServer(defaultBrowserOptions); | ||||||
|   expect(browserServer.process().pid).toBeGreaterThan(0); |   expect(browserServer.process().pid).toBeGreaterThan(0); | ||||||
|   await browserServer.close(); |   await browserServer.close(); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| it('should fire close event', async ({browserType, defaultBrowserOptions}) => { | it.skip(USES_HOOKS)('should fire close event', async ({browserType, defaultBrowserOptions}) => { | ||||||
|   const browserServer = await browserType.launchServer(defaultBrowserOptions); |   const browserServer = await browserType.launchServer(defaultBrowserOptions); | ||||||
|   const [result] = await Promise.all([ |   const [result] = await Promise.all([ | ||||||
|     new Promise(f => (browserServer as any).on('close', (exitCode, signal) => f({ exitCode, signal }))), |     new Promise(f => (browserServer as any).on('close', (exitCode, signal) => f({ exitCode, signal }))), | ||||||
| @ -78,7 +76,7 @@ it('should fire close event', async ({browserType, defaultBrowserOptions}) => { | |||||||
|   expect(result['signal']).toBe(null); |   expect(result['signal']).toBe(null); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| it('should reject navigation when browser closes', async({browserType, defaultBrowserOptions, server, toImpl}) => { | it.skip(USES_HOOKS)('should reject navigation when browser closes', async({browserType, defaultBrowserOptions, server}) => { | ||||||
|   server.setRoute('/one-style.css', () => {}); |   server.setRoute('/one-style.css', () => {}); | ||||||
|   const browserServer = await browserType.launchServer(defaultBrowserOptions); |   const browserServer = await browserType.launchServer(defaultBrowserOptions); | ||||||
|   const remote = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() }); |   const remote = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() }); | ||||||
| @ -88,12 +86,10 @@ it('should reject navigation when browser closes', async({browserType, defaultBr | |||||||
|   await remote.close(); |   await remote.close(); | ||||||
|   const error = await navigationPromise; |   const error = await navigationPromise; | ||||||
|   expect(error.message).toContain('Navigation failed because page was closed!'); |   expect(error.message).toContain('Navigation failed because page was closed!'); | ||||||
|   if (toImpl) |  | ||||||
|     await toImpl(browserServer)._checkLeaks(); |  | ||||||
|   await browserServer.close(); |   await browserServer.close(); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| it('should reject waitForSelector when browser closes', async({browserType, defaultBrowserOptions, server, toImpl}) => { | it.skip(USES_HOOKS)('should reject waitForSelector when browser closes', async({browserType, defaultBrowserOptions, server}) => { | ||||||
|   server.setRoute('/empty.html', () => {}); |   server.setRoute('/empty.html', () => {}); | ||||||
|   const browserServer = await browserType.launchServer(defaultBrowserOptions); |   const browserServer = await browserType.launchServer(defaultBrowserOptions); | ||||||
|   const remote = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() }); |   const remote = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() }); | ||||||
| @ -106,24 +102,20 @@ it('should reject waitForSelector when browser closes', async({browserType, defa | |||||||
|   await remote.close(); |   await remote.close(); | ||||||
|   const error = await watchdog; |   const error = await watchdog; | ||||||
|   expect(error.message).toContain('Protocol error'); |   expect(error.message).toContain('Protocol error'); | ||||||
|   if (toImpl) |  | ||||||
|     await toImpl(browserServer)._checkLeaks(); |  | ||||||
|   await browserServer.close(); |   await browserServer.close(); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| it('should throw if used after disconnect', async({browserType, defaultBrowserOptions, toImpl}) => { | it.skip(USES_HOOKS)('should throw if used after disconnect', async({browserType, defaultBrowserOptions}) => { | ||||||
|   const browserServer = await browserType.launchServer(defaultBrowserOptions); |   const browserServer = await browserType.launchServer(defaultBrowserOptions); | ||||||
|   const remote = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() }); |   const remote = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() }); | ||||||
|   const page = await remote.newPage(); |   const page = await remote.newPage(); | ||||||
|   await remote.close(); |   await remote.close(); | ||||||
|   const error = await page.evaluate('1 + 1').catch(e => e); |   const error = await page.evaluate('1 + 1').catch(e => e); | ||||||
|   expect((error as Error).message).toContain('has been closed'); |   expect((error as Error).message).toContain('has been closed'); | ||||||
|   if (toImpl) |  | ||||||
|     await toImpl(browserServer)._checkLeaks(); |  | ||||||
|   await browserServer.close(); |   await browserServer.close(); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| it('should emit close events on pages and contexts', async({browserType, defaultBrowserOptions}) => { | it.skip(USES_HOOKS)('should emit close events on pages and contexts', async({browserType, defaultBrowserOptions}) => { | ||||||
|   const browserServer = await browserType.launchServer(defaultBrowserOptions); |   const browserServer = await browserType.launchServer(defaultBrowserOptions); | ||||||
|   const remote = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() }); |   const remote = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() }); | ||||||
|   const context = await remote.newContext(); |   const context = await remote.newContext(); | ||||||
| @ -137,7 +129,7 @@ it('should emit close events on pages and contexts', async({browserType, default | |||||||
|   expect(pageClosed).toBeTruthy(); |   expect(pageClosed).toBeTruthy(); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| it('should terminate network waiters', async({browserType, defaultBrowserOptions, server}) => { | it.skip(USES_HOOKS)('should terminate network waiters', async({browserType, defaultBrowserOptions, server}) => { | ||||||
|   const browserServer = await browserType.launchServer(defaultBrowserOptions); |   const browserServer = await browserType.launchServer(defaultBrowserOptions); | ||||||
|   const remote = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() }); |   const remote = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() }); | ||||||
|   const newPage = await remote.newPage(); |   const newPage = await remote.newPage(); | ||||||
|  | |||||||
| @ -21,14 +21,14 @@ import { ChromiumBrowser, ChromiumBrowserContext } from '../..'; | |||||||
| const {makeUserDataDir, removeUserDataDir} = utils; | const {makeUserDataDir, removeUserDataDir} = utils; | ||||||
| const {FFOX, CHROMIUM, WEBKIT, WIN, USES_HOOKS} = testOptions; | const {FFOX, CHROMIUM, WEBKIT, WIN, USES_HOOKS} = testOptions; | ||||||
| 
 | 
 | ||||||
| it.skip(!CHROMIUM)('should throw with remote-debugging-pipe argument', async({browserType, defaultBrowserOptions}) => { | it.skip(USES_HOOKS || !CHROMIUM)('should throw with remote-debugging-pipe argument', async({browserType, defaultBrowserOptions}) => { | ||||||
|   const options = Object.assign({}, defaultBrowserOptions); |   const options = Object.assign({}, defaultBrowserOptions); | ||||||
|   options.args = ['--remote-debugging-pipe'].concat(options.args || []); |   options.args = ['--remote-debugging-pipe'].concat(options.args || []); | ||||||
|   const error = await browserType.launchServer(options).catch(e => e); |   const error = await browserType.launchServer(options).catch(e => e); | ||||||
|   expect(error.message).toContain('Playwright manages remote debugging connection itself'); |   expect(error.message).toContain('Playwright manages remote debugging connection itself'); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| it.skip(!CHROMIUM)('should not throw with remote-debugging-port argument', async({browserType, defaultBrowserOptions}) => { | it.skip(USES_HOOKS || !CHROMIUM)('should not throw with remote-debugging-port argument', async({browserType, defaultBrowserOptions}) => { | ||||||
|   const options = Object.assign({}, defaultBrowserOptions); |   const options = Object.assign({}, defaultBrowserOptions); | ||||||
|   options.args = ['--remote-debugging-port=0'].concat(options.args || []); |   options.args = ['--remote-debugging-port=0'].concat(options.args || []); | ||||||
|   const browser = await browserType.launchServer(options); |   const browser = await browserType.launchServer(options); | ||||||
|  | |||||||
| @ -16,9 +16,9 @@ | |||||||
|  */ |  */ | ||||||
| import './base.fixture'; | import './base.fixture'; | ||||||
| 
 | 
 | ||||||
| const {FFOX, CHROMIUM, WEBKIT} = testOptions; | const {FFOX, CHROMIUM, WEBKIT, USES_HOOKS} = testOptions; | ||||||
| 
 | 
 | ||||||
| it('should work across sessions', async ({browserType, defaultBrowserOptions, toImpl}) => { | it.skip(USES_HOOKS)('should work across sessions', async ({browserType, defaultBrowserOptions}) => { | ||||||
|   const browserServer = await browserType.launchServer(defaultBrowserOptions); |   const browserServer = await browserType.launchServer(defaultBrowserOptions); | ||||||
|   const browser1 = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() }); |   const browser1 = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() }); | ||||||
|   expect(browser1.contexts().length).toBe(0); |   expect(browser1.contexts().length).toBe(0); | ||||||
| @ -35,12 +35,10 @@ it('should work across sessions', async ({browserType, defaultBrowserOptions, to | |||||||
|   await browser1.close(); |   await browser1.close(); | ||||||
|   await browser2.close(); |   await browser2.close(); | ||||||
| 
 | 
 | ||||||
|   if (toImpl) |  | ||||||
|     await toImpl(browserServer)._checkLeaks(); |  | ||||||
|   await browserServer.close(); |   await browserServer.close(); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| it.slow()('should be emitted when: browser gets closed, disconnected or underlying websocket gets closed', async ({browserType, defaultBrowserOptions}) => { | it.skip(USES_HOOKS).slow()('should be emitted when: browser gets closed, disconnected or underlying websocket gets closed', async ({browserType, defaultBrowserOptions}) => { | ||||||
|   const browserServer = await browserType.launchServer(defaultBrowserOptions); |   const browserServer = await browserType.launchServer(defaultBrowserOptions); | ||||||
|   const originalBrowser = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() }); |   const originalBrowser = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() }); | ||||||
|   const wsEndpoint = browserServer.wsEndpoint(); |   const wsEndpoint = browserServer.wsEndpoint(); | ||||||
| @ -74,7 +72,7 @@ it.slow()('should be emitted when: browser gets closed, disconnected or underlyi | |||||||
|   expect(disconnectedRemote2).toBe(1); |   expect(disconnectedRemote2).toBe(1); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| it('should be able to connect multiple times to the same browser', async({browserType, defaultBrowserOptions, toImpl}) => { | it.skip(USES_HOOKS)('should be able to connect multiple times to the same browser', async({browserType, defaultBrowserOptions}) => { | ||||||
|   const browserServer = await browserType.launchServer(defaultBrowserOptions); |   const browserServer = await browserType.launchServer(defaultBrowserOptions); | ||||||
|   const browser1 = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() }); |   const browser1 = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() }); | ||||||
|   const browser2 = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() }); |   const browser2 = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() }); | ||||||
| @ -85,12 +83,10 @@ it('should be able to connect multiple times to the same browser', async({browse | |||||||
|   const page2 = await browser2.newPage(); |   const page2 = await browser2.newPage(); | ||||||
|   expect(await page2.evaluate(() => 7 * 6)).toBe(42); // original browser should still work
 |   expect(await page2.evaluate(() => 7 * 6)).toBe(42); // original browser should still work
 | ||||||
|   await browser2.close(); |   await browser2.close(); | ||||||
|   if (toImpl) |  | ||||||
|     await toImpl(browserServer)._checkLeaks(); |  | ||||||
|   await browserServer.close(); |   await browserServer.close(); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| it('should not be able to close remote browser', async({browserType, defaultBrowserOptions, toImpl}) => { | it.skip(USES_HOOKS)('should not be able to close remote browser', async({browserType, defaultBrowserOptions}) => { | ||||||
|   const browserServer = await browserType.launchServer(defaultBrowserOptions); |   const browserServer = await browserType.launchServer(defaultBrowserOptions); | ||||||
|   { |   { | ||||||
|     const remote = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() }); |     const remote = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() }); | ||||||
| @ -102,7 +98,5 @@ it('should not be able to close remote browser', async({browserType, defaultBrow | |||||||
|     await remote.newContext(); |     await remote.newContext(); | ||||||
|     await remote.close(); |     await remote.close(); | ||||||
|   } |   } | ||||||
|   if (toImpl) |  | ||||||
|     await toImpl(browserServer)._checkLeaks(); |  | ||||||
|   await browserServer.close(); |   await browserServer.close(); | ||||||
| }); | }); | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Dmitry Gozman
						Dmitry Gozman