mirror of
				https://github.com/microsoft/playwright.git
				synced 2025-06-26 21:40:17 +00:00 
			
		
		
		
	feat(connect): pw:server:channel and pw:socks debug logs (#23144)
				
					
				
			Fixes #23081.
This commit is contained in:
		
							parent
							
								
									ab7e794bf7
								
							
						
					
					
						commit
						422b65eeae
					
				| @ -23,11 +23,13 @@ const debugLoggerColorMap = { | ||||
|   'install': 34, // green
 | ||||
|   'download': 34, // green
 | ||||
|   'browser': 0, // reset
 | ||||
|   'proxy': 92, // purple
 | ||||
|   'socks': 92, // purple
 | ||||
|   'error': 160, // red,
 | ||||
|   'channel:command': 33, // blue
 | ||||
|   'channel:response': 202, // orange
 | ||||
|   'channel:event': 207, // magenta
 | ||||
|   'server': 45, // cyan
 | ||||
|   'server:channel': 34, // green
 | ||||
| }; | ||||
| export type LogName = keyof typeof debugLoggerColorMap; | ||||
| 
 | ||||
|  | ||||
| @ -436,7 +436,6 @@ export class SocksProxy extends EventEmitter implements SocksConnectionClient { | ||||
|       this._server.listen(port, () => { | ||||
|         const port = (this._server.address() as AddressInfo).port; | ||||
|         this._port = port; | ||||
|         debugLogger.log('proxy', `Starting socks proxy server on port ${port}`); | ||||
|         f(port); | ||||
|       }); | ||||
|     }); | ||||
| @ -525,8 +524,10 @@ export class SocksProxyHandler extends EventEmitter { | ||||
|   } | ||||
| 
 | ||||
|   async socketRequested({ uid, host, port }: SocksSocketRequestedPayload): Promise<void> { | ||||
|     debugLogger.log('socks', `[${uid}] => request ${host}:${port}`); | ||||
|     if (!this._patternMatcher(host, port)) { | ||||
|       const payload: SocksSocketFailedPayload = { uid, errorCode: 'ERULESET' }; | ||||
|       debugLogger.log('socks', `[${uid}] <= pattern error ${payload.errorCode}`); | ||||
|       this.emit(SocksProxyHandler.Events.SocksFailed, payload); | ||||
|       return; | ||||
|     } | ||||
| @ -543,11 +544,13 @@ export class SocksProxyHandler extends EventEmitter { | ||||
|       }); | ||||
|       socket.on('error', error => { | ||||
|         const payload: SocksSocketErrorPayload = { uid, error: error.message }; | ||||
|         debugLogger.log('socks', `[${uid}] <= network socket error ${payload.error}`); | ||||
|         this.emit(SocksProxyHandler.Events.SocksError, payload); | ||||
|         this._sockets.delete(uid); | ||||
|       }); | ||||
|       socket.on('end', () => { | ||||
|         const payload: SocksSocketEndPayload = { uid }; | ||||
|         debugLogger.log('socks', `[${uid}] <= network socket closed`); | ||||
|         this.emit(SocksProxyHandler.Events.SocksEnd, payload); | ||||
|         this._sockets.delete(uid); | ||||
|       }); | ||||
| @ -555,9 +558,11 @@ export class SocksProxyHandler extends EventEmitter { | ||||
|       const localPort = socket.localPort; | ||||
|       this._sockets.set(uid, socket); | ||||
|       const payload: SocksSocketConnectedPayload = { uid, host: localAddress, port: localPort }; | ||||
|       debugLogger.log('socks', `[${uid}] <= connected to network ${payload.host}:${payload.port}`); | ||||
|       this.emit(SocksProxyHandler.Events.SocksConnected, payload); | ||||
|     } catch (error) { | ||||
|       const payload: SocksSocketFailedPayload = { uid, errorCode: error.code }; | ||||
|       debugLogger.log('socks', `[${uid}] <= connect error ${payload.errorCode}`); | ||||
|       this.emit(SocksProxyHandler.Events.SocksFailed, payload); | ||||
|     } | ||||
|   } | ||||
| @ -567,6 +572,7 @@ export class SocksProxyHandler extends EventEmitter { | ||||
|   } | ||||
| 
 | ||||
|   socketClosed({ uid }: SocksSocketClosedPayload): void { | ||||
|     debugLogger.log('socks', `[${uid}] <= browser socket closed`); | ||||
|     this._sockets.get(uid)?.destroy(); | ||||
|     this._sockets.delete(uid); | ||||
|   } | ||||
|  | ||||
| @ -25,6 +25,8 @@ import type { LaunchOptions } from '../server/types'; | ||||
| import { AndroidDevice } from '../server/android/android'; | ||||
| import { DebugControllerDispatcher } from '../server/dispatchers/debugControllerDispatcher'; | ||||
| import { startProfiling, stopProfiling } from '../utils'; | ||||
| import { monotonicTime } from '../utils'; | ||||
| import { debugLogger } from '../common/debugLogger'; | ||||
| 
 | ||||
| export type ClientType = 'controller' | 'playwright' | 'launch-browser' | 'reuse-browser' | 'pre-launched-browser-or-android'; | ||||
| 
 | ||||
| @ -46,14 +48,14 @@ export class PlaywrightConnection { | ||||
|   private _onClose: () => void; | ||||
|   private _dispatcherConnection: DispatcherConnection; | ||||
|   private _cleanups: (() => Promise<void>)[] = []; | ||||
|   private _debugLog: (m: string) => void; | ||||
|   private _id: string; | ||||
|   private _disconnected = false; | ||||
|   private _preLaunched: PreLaunched; | ||||
|   private _options: Options; | ||||
|   private _root: DispatcherScope; | ||||
|   private _profileName: string; | ||||
| 
 | ||||
|   constructor(lock: Promise<void>, clientType: ClientType, ws: WebSocket, options: Options, preLaunched: PreLaunched, log: (m: string) => void, onClose: () => void) { | ||||
|   constructor(lock: Promise<void>, clientType: ClientType, ws: WebSocket, options: Options, preLaunched: PreLaunched, id: string, onClose: () => void) { | ||||
|     this._ws = ws; | ||||
|     this._preLaunched = preLaunched; | ||||
|     this._options = options; | ||||
| @ -63,18 +65,25 @@ export class PlaywrightConnection { | ||||
|     if (clientType === 'pre-launched-browser-or-android') | ||||
|       assert(preLaunched.browser || preLaunched.androidDevice); | ||||
|     this._onClose = onClose; | ||||
|     this._debugLog = log; | ||||
|     this._id = id; | ||||
|     this._profileName = `${new Date().toISOString()}-${clientType}`; | ||||
| 
 | ||||
|     this._dispatcherConnection = new DispatcherConnection(); | ||||
|     this._dispatcherConnection.onmessage = async message => { | ||||
|       await lock; | ||||
|       if (ws.readyState !== ws.CLOSING) | ||||
|         ws.send(JSON.stringify(message)); | ||||
|       if (ws.readyState !== ws.CLOSING) { | ||||
|         const messageString = JSON.stringify(message); | ||||
|         if (debugLogger.isEnabled('server:channel')) | ||||
|           debugLogger.log('server:channel', `[${this._id}] ${monotonicTime() * 1000} SEND ► ${messageString}`); | ||||
|         ws.send(messageString); | ||||
|       } | ||||
|     }; | ||||
|     ws.on('message', async (message: string) => { | ||||
|       await lock; | ||||
|       this._dispatcherConnection.dispatch(JSON.parse(Buffer.from(message).toString())); | ||||
|       const messageString = Buffer.from(message).toString(); | ||||
|       if (debugLogger.isEnabled('server:channel')) | ||||
|         debugLogger.log('server:channel', `[${this._id}] ${monotonicTime() * 1000} ◀ RECV ${messageString}`); | ||||
|       this._dispatcherConnection.dispatch(JSON.parse(messageString)); | ||||
|     }); | ||||
| 
 | ||||
|     ws.on('close', () => this._onDisconnect()); | ||||
| @ -100,7 +109,7 @@ export class PlaywrightConnection { | ||||
|   } | ||||
| 
 | ||||
|   private async _initPlaywrightConnectMode(scope: RootDispatcher) { | ||||
|     this._debugLog(`engaged playwright.connect mode`); | ||||
|     debugLogger.log('server', `[${this._id}] engaged playwright.connect mode`); | ||||
|     const playwright = createPlaywright('javascript'); | ||||
|     // Close all launched browsers on disconnect.
 | ||||
|     this._cleanups.push(async () => { | ||||
| @ -112,7 +121,7 @@ export class PlaywrightConnection { | ||||
|   } | ||||
| 
 | ||||
|   private async _initLaunchBrowserMode(scope: RootDispatcher) { | ||||
|     this._debugLog(`engaged launch mode for "${this._options.browserName}"`); | ||||
|     debugLogger.log('server', `[${this._id}] engaged launch mode for "${this._options.browserName}"`); | ||||
|     const playwright = createPlaywright('javascript'); | ||||
| 
 | ||||
|     const ownedSocksProxy = await this._createOwnedSocksProxy(playwright); | ||||
| @ -131,7 +140,7 @@ export class PlaywrightConnection { | ||||
|   } | ||||
| 
 | ||||
|   private async _initPreLaunchedBrowserMode(scope: RootDispatcher) { | ||||
|     this._debugLog(`engaged pre-launched (browser) mode`); | ||||
|     debugLogger.log('server', `[${this._id}] engaged pre-launched (browser) mode`); | ||||
|     const playwright = this._preLaunched.playwright!; | ||||
| 
 | ||||
|     // Note: connected client owns the socks proxy and configures the pattern.
 | ||||
| @ -154,7 +163,7 @@ export class PlaywrightConnection { | ||||
|   } | ||||
| 
 | ||||
|   private async _initPreLaunchedAndroidMode(scope: RootDispatcher) { | ||||
|     this._debugLog(`engaged pre-launched (Android) mode`); | ||||
|     debugLogger.log('server', `[${this._id}] engaged pre-launched (Android) mode`); | ||||
|     const playwright = this._preLaunched.playwright!; | ||||
|     const androidDevice = this._preLaunched.androidDevice!; | ||||
|     androidDevice.on(AndroidDevice.Events.Close, () => { | ||||
| @ -167,7 +176,7 @@ export class PlaywrightConnection { | ||||
|   } | ||||
| 
 | ||||
|   private _initDebugControllerMode(): DebugControllerDispatcher { | ||||
|     this._debugLog(`engaged reuse controller mode`); | ||||
|     debugLogger.log('server', `[${this._id}] engaged reuse controller mode`); | ||||
|     const playwright = this._preLaunched.playwright!; | ||||
|     // Always create new instance based on the reused Playwright instance.
 | ||||
|     return new DebugControllerDispatcher(this._dispatcherConnection, playwright.debugController); | ||||
| @ -177,7 +186,7 @@ export class PlaywrightConnection { | ||||
|     // Note: reuse browser mode does not support socks proxy, because
 | ||||
|     // clients come and go, while the browser stays the same.
 | ||||
| 
 | ||||
|     this._debugLog(`engaged reuse browsers mode for ${this._options.browserName}`); | ||||
|     debugLogger.log('server', `[${this._id}] engaged reuse browsers mode for ${this._options.browserName}`); | ||||
|     const playwright = this._preLaunched.playwright!; | ||||
| 
 | ||||
|     const requestedOptions = launchOptionsHash(this._options.launchOptions); | ||||
| @ -232,27 +241,27 @@ export class PlaywrightConnection { | ||||
|     const socksProxy = new SocksProxy(); | ||||
|     socksProxy.setPattern(this._options.socksProxyPattern); | ||||
|     playwright.options.socksProxyPort = await socksProxy.listen(0); | ||||
|     this._debugLog(`started socks proxy on port ${playwright.options.socksProxyPort}`); | ||||
|     debugLogger.log('server', `[${this._id}] started socks proxy on port ${playwright.options.socksProxyPort}`); | ||||
|     this._cleanups.push(() => socksProxy.close()); | ||||
|     return socksProxy; | ||||
|   } | ||||
| 
 | ||||
|   private async _onDisconnect(error?: Error) { | ||||
|     this._disconnected = true; | ||||
|     this._debugLog(`disconnected. error: ${error}`); | ||||
|     debugLogger.log('server', `[${this._id}] disconnected. error: ${error}`); | ||||
|     this._root._dispose(); | ||||
|     this._debugLog(`starting cleanup`); | ||||
|     debugLogger.log('server', `[${this._id}] starting cleanup`); | ||||
|     for (const cleanup of this._cleanups) | ||||
|       await cleanup().catch(() => {}); | ||||
|     await stopProfiling(this._profileName); | ||||
|     this._onClose(); | ||||
|     this._debugLog(`finished cleanup`); | ||||
|     debugLogger.log('server', `[${this._id}] finished cleanup`); | ||||
|   } | ||||
| 
 | ||||
|   async close(reason?: { code: number, reason: string }) { | ||||
|     if (this._disconnected) | ||||
|       return; | ||||
|     this._debugLog(`force closing connection: ${reason?.reason || ''} (${reason?.code || 0})`); | ||||
|     debugLogger.log('server', `[${this._id}] force closing connection: ${reason?.reason || ''} (${reason?.code || 0})`); | ||||
|     try { | ||||
|       this._ws.close(reason?.code, reason?.reason); | ||||
|     } catch (e) { | ||||
|  | ||||
| @ -14,7 +14,7 @@ | ||||
|  * limitations under the License. | ||||
|  */ | ||||
| 
 | ||||
| import { debug, wsServer } from '../utilsBundle'; | ||||
| import { wsServer } from '../utilsBundle'; | ||||
| import type { WebSocketServer } from '../utilsBundle'; | ||||
| import http from 'http'; | ||||
| import type { Browser } from '../server/browser'; | ||||
| @ -26,17 +26,11 @@ import type  { LaunchOptions } from '../server/types'; | ||||
| import { ManualPromise } from '../utils/manualPromise'; | ||||
| import type { AndroidDevice } from '../server/android/android'; | ||||
| import { type SocksProxy } from '../common/socksProxy'; | ||||
| 
 | ||||
| const debugLog = debug('pw:server'); | ||||
| import { debugLogger } from '../common/debugLogger'; | ||||
| 
 | ||||
| let lastConnectionId = 0; | ||||
| const kConnectionSymbol = Symbol('kConnection'); | ||||
| 
 | ||||
| function newLogger() { | ||||
|   const id = ++lastConnectionId; | ||||
|   return (message: string) => debugLog(`[id=${id}] ${message}`); | ||||
| } | ||||
| 
 | ||||
| type ServerOptions = { | ||||
|   path: string; | ||||
|   maxConnections: number; | ||||
| @ -59,6 +53,8 @@ export class PlaywrightServer { | ||||
|   } | ||||
| 
 | ||||
|   async listen(port: number = 0): Promise<string> { | ||||
|     debugLogger.log('server', `Server started at ${new Date()}`); | ||||
| 
 | ||||
|     const server = http.createServer((request: http.IncomingMessage, response: http.ServerResponse) => { | ||||
|       if (request.method === 'GET' && request.url === '/json') { | ||||
|         response.setHeader('Content-Type', 'application/json'); | ||||
| @ -69,7 +65,7 @@ export class PlaywrightServer { | ||||
|       } | ||||
|       response.end('Running'); | ||||
|     }); | ||||
|     server.on('error', error => debugLog(error)); | ||||
|     server.on('error', error => debugLogger.log('server', String(error))); | ||||
| 
 | ||||
|     const wsEndpoint = await new Promise<string>((resolve, reject) => { | ||||
|       server.listen(port, () => { | ||||
| @ -83,7 +79,7 @@ export class PlaywrightServer { | ||||
|       }).on('error', reject); | ||||
|     }); | ||||
| 
 | ||||
|     debugLog('Listening at ' + wsEndpoint); | ||||
|     debugLogger.log('server', 'Listening at ' + wsEndpoint); | ||||
|     this._wsServer = new wsServer({ server, path: this._options.path }); | ||||
|     const browserSemaphore = new Semaphore(this._options.maxConnections); | ||||
|     const controllerSemaphore = new Semaphore(1); | ||||
| @ -107,8 +103,8 @@ export class PlaywrightServer { | ||||
|       } catch (e) { | ||||
|       } | ||||
| 
 | ||||
|       const log = newLogger(); | ||||
|       log(`serving connection: ${request.url}`); | ||||
|       const id = String(++lastConnectionId); | ||||
|       debugLogger.log('server', `[${id}] serving connection: ${request.url}`); | ||||
|       const isDebugControllerClient = !!request.headers['x-playwright-debug-controller']; | ||||
|       const shouldReuseBrowser = !!request.headers['x-playwright-reuse-context']; | ||||
| 
 | ||||
| @ -145,7 +141,7 @@ export class PlaywrightServer { | ||||
|             androidDevice: this._options.preLaunchedAndroidDevice, | ||||
|             socksProxy: this._options.preLaunchedSocksProxy, | ||||
|           }, | ||||
|           log, () => semaphore.release()); | ||||
|           id, () => semaphore.release()); | ||||
|       (ws as any)[kConnectionSymbol] = connection; | ||||
|     }); | ||||
| 
 | ||||
| @ -156,7 +152,7 @@ export class PlaywrightServer { | ||||
|     const server = this._wsServer; | ||||
|     if (!server) | ||||
|       return; | ||||
|     debugLog('closing websocket server'); | ||||
|     debugLogger.log('server', 'closing websocket server'); | ||||
|     const waitForClose = new Promise(f => server.close(f)); | ||||
|     // First disconnect all remaining clients.
 | ||||
|     await Promise.all(Array.from(server.clients).map(async ws => { | ||||
| @ -169,15 +165,15 @@ export class PlaywrightServer { | ||||
|       } | ||||
|     })); | ||||
|     await waitForClose; | ||||
|     debugLog('closing http server'); | ||||
|     debugLogger.log('server', 'closing http server'); | ||||
|     await new Promise(f => server.options.server!.close(f)); | ||||
|     this._wsServer = undefined; | ||||
|     debugLog('closed server'); | ||||
|     debugLogger.log('server', 'closed server'); | ||||
| 
 | ||||
|     debugLog('closing browsers'); | ||||
|     debugLogger.log('server', 'closing browsers'); | ||||
|     if (this._preLaunchedPlaywright) | ||||
|       await Promise.all(this._preLaunchedPlaywright.allBrowsers().map(browser => browser.close())); | ||||
|     debugLog('closed browsers'); | ||||
|     debugLogger.log('server', 'closed browsers'); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Dmitry Gozman
						Dmitry Gozman