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