mirror of
https://github.com/microsoft/playwright.git
synced 2024-12-04 16:44:11 +03:00
chore: simplify remote connection protocol (#6164)
This changes the root object from RemoteBrowser to Playwright, similar to local driver connection. This way, any remote connection gets a Playwright object. This also starts reusing PlaywrightServer class, and introduces `cli run-server` hidden command that runs ws server on the specified port. Previous structure: ``` RemoteBrowser - browser (using ConnectedBrowser for remote-specific behavior) - selectors (special instance for this remote connection) ``` New structure: ``` Playwright - ... - selectors (special instance for this remote connection) - preLaunchedBrowser (using ConnectedBrowser for remote-specific behavior) ```
This commit is contained in:
parent
c4c9809f85
commit
fff1f3d45c
@ -16,11 +16,9 @@
|
||||
|
||||
import { LaunchServerOptions, Logger } from './client/types';
|
||||
import { BrowserType } from './server/browserType';
|
||||
import * as ws from 'ws';
|
||||
import { Browser } from './server/browser';
|
||||
import { ChildProcess } from 'child_process';
|
||||
import { EventEmitter } from 'ws';
|
||||
import { Dispatcher, DispatcherScope, DispatcherConnection } from './dispatchers/dispatcher';
|
||||
import { DispatcherScope } from './dispatchers/dispatcher';
|
||||
import { BrowserDispatcher } from './dispatchers/browserDispatcher';
|
||||
import { BrowserContextDispatcher } from './dispatchers/browserContextDispatcher';
|
||||
import * as channels from './protocol/channels';
|
||||
@ -30,123 +28,69 @@ import { createGuid } from './utils/utils';
|
||||
import { SelectorsDispatcher } from './dispatchers/selectorsDispatcher';
|
||||
import { Selectors } from './server/selectors';
|
||||
import { ProtocolLogger } from './server/types';
|
||||
import { CallMetadata, internalCallMetadata, SdkObject } from './server/instrumentation';
|
||||
import { CallMetadata, internalCallMetadata } from './server/instrumentation';
|
||||
import { Playwright } from './server/playwright';
|
||||
import { PlaywrightDispatcher } from './dispatchers/playwrightDispatcher';
|
||||
import { PlaywrightServer, PlaywrightServerDelegate } from './remote/playwrightServer';
|
||||
|
||||
export class BrowserServerLauncherImpl implements BrowserServerLauncher {
|
||||
private _playwright: Playwright;
|
||||
private _browserType: BrowserType;
|
||||
|
||||
constructor(browserType: BrowserType) {
|
||||
constructor(playwright: Playwright, browserType: BrowserType) {
|
||||
this._playwright = playwright;
|
||||
this._browserType = browserType;
|
||||
}
|
||||
|
||||
async launchServer(options: LaunchServerOptions = {}): Promise<BrowserServerImpl> {
|
||||
async launchServer(options: LaunchServerOptions = {}): Promise<BrowserServer> {
|
||||
// 1. Pre-launch the browser
|
||||
const browser = await this._browserType.launch(internalCallMetadata(), {
|
||||
...options,
|
||||
ignoreDefaultArgs: Array.isArray(options.ignoreDefaultArgs) ? options.ignoreDefaultArgs : undefined,
|
||||
ignoreAllDefaultArgs: !!options.ignoreDefaultArgs && !Array.isArray(options.ignoreDefaultArgs),
|
||||
env: options.env ? envObjectToArray(options.env) : undefined,
|
||||
}, toProtocolLogger(options.logger));
|
||||
return BrowserServerImpl.start(browser, options.port);
|
||||
}
|
||||
}
|
||||
|
||||
export class BrowserServerImpl extends EventEmitter implements BrowserServer {
|
||||
private _server: ws.Server;
|
||||
private _browser: Browser;
|
||||
private _wsEndpoint: string;
|
||||
private _process: ChildProcess;
|
||||
private _ready: Promise<void>;
|
||||
|
||||
static async start(browser: Browser, port: number = 0): Promise<BrowserServerImpl> {
|
||||
const server = new BrowserServerImpl(browser, port);
|
||||
await server._ready;
|
||||
return server;
|
||||
}
|
||||
|
||||
constructor(browser: Browser, port: number) {
|
||||
super();
|
||||
|
||||
this._browser = browser;
|
||||
this._wsEndpoint = '';
|
||||
this._process = browser.options.browserProcess.process!;
|
||||
|
||||
let readyCallback = () => {};
|
||||
this._ready = new Promise<void>(f => readyCallback = f);
|
||||
|
||||
const token = createGuid();
|
||||
this._server = new ws.Server({ port, path: '/' + token }, () => {
|
||||
const address = this._server.address();
|
||||
this._wsEndpoint = typeof address === 'string' ? `${address}/${token}` : `ws://127.0.0.1:${address.port}/${token}`;
|
||||
readyCallback();
|
||||
});
|
||||
|
||||
this._server.on('connection', (socket: ws, req) => {
|
||||
this._clientAttached(socket);
|
||||
});
|
||||
|
||||
browser.options.browserProcess.onclose = (exitCode, signal) => {
|
||||
this._server.close();
|
||||
this.emit('close', exitCode, signal);
|
||||
// 2. Start the server
|
||||
const delegate: PlaywrightServerDelegate = {
|
||||
path: '/' + createGuid(),
|
||||
allowMultipleClients: true,
|
||||
onClose: () => {},
|
||||
onConnect: this._onConnect.bind(this, browser),
|
||||
};
|
||||
}
|
||||
const server = new PlaywrightServer(delegate);
|
||||
const wsEndpoint = await server.listen(options.port);
|
||||
|
||||
process(): ChildProcess {
|
||||
return this._process;
|
||||
}
|
||||
|
||||
wsEndpoint(): string {
|
||||
return this._wsEndpoint;
|
||||
}
|
||||
|
||||
async close(): Promise<void> {
|
||||
await this._browser.options.browserProcess.close();
|
||||
}
|
||||
|
||||
async kill(): Promise<void> {
|
||||
await this._browser.options.browserProcess.kill();
|
||||
}
|
||||
|
||||
private _clientAttached(socket: ws) {
|
||||
const connection = new DispatcherConnection();
|
||||
connection.onmessage = message => {
|
||||
if (socket.readyState !== ws.CLOSING)
|
||||
socket.send(JSON.stringify(message));
|
||||
// 3. Return the BrowserServer interface
|
||||
const browserServer = new EventEmitter() as (BrowserServer & EventEmitter);
|
||||
browserServer.process = () => browser.options.browserProcess.process!;
|
||||
browserServer.wsEndpoint = () => wsEndpoint;
|
||||
browserServer.close = () => browser.options.browserProcess.close();
|
||||
browserServer.kill = () => browser.options.browserProcess.kill();
|
||||
browser.options.browserProcess.onclose = async (exitCode, signal) => {
|
||||
server.close();
|
||||
browserServer.emit('close', exitCode, signal);
|
||||
};
|
||||
socket.on('message', (message: string) => {
|
||||
connection.dispatch(JSON.parse(Buffer.from(message).toString()));
|
||||
});
|
||||
socket.on('error', () => {});
|
||||
return browserServer;
|
||||
}
|
||||
|
||||
private _onConnect(browser: Browser, scope: DispatcherScope) {
|
||||
const selectors = new Selectors();
|
||||
const scope = connection.rootDispatcher();
|
||||
const remoteBrowser = new RemoteBrowserDispatcher(scope, this._browser, selectors);
|
||||
socket.on('close', () => {
|
||||
// Avoid sending any more messages over closed socket.
|
||||
connection.onmessage = () => {};
|
||||
const selectorsDispatcher = new SelectorsDispatcher(scope, selectors);
|
||||
const browserDispatcher = new ConnectedBrowser(scope, browser, selectors);
|
||||
new PlaywrightDispatcher(scope, this._playwright, selectorsDispatcher, browserDispatcher);
|
||||
return () => {
|
||||
// Cleanup contexts upon disconnect.
|
||||
remoteBrowser.connectedBrowser.close().catch(e => {});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class RemoteBrowserDispatcher extends Dispatcher<SdkObject, channels.RemoteBrowserInitializer> implements channels.PlaywrightChannel {
|
||||
readonly connectedBrowser: ConnectedBrowser;
|
||||
|
||||
constructor(scope: DispatcherScope, browser: Browser, selectors: Selectors) {
|
||||
const connectedBrowser = new ConnectedBrowser(scope, browser, selectors);
|
||||
super(scope, browser, 'RemoteBrowser', {
|
||||
selectors: new SelectorsDispatcher(scope, selectors),
|
||||
browser: connectedBrowser,
|
||||
}, false, 'remoteBrowser');
|
||||
this.connectedBrowser = connectedBrowser;
|
||||
connectedBrowser._remoteBrowser = this;
|
||||
browserDispatcher.close().catch(e => {});
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// This class implements multiplexing multiple BrowserDispatchers over a single Browser instance.
|
||||
class ConnectedBrowser extends BrowserDispatcher {
|
||||
private _contexts: BrowserContextDispatcher[] = [];
|
||||
private _selectors: Selectors;
|
||||
_closed = false;
|
||||
_remoteBrowser?: RemoteBrowserDispatcher;
|
||||
private _closed = false;
|
||||
|
||||
constructor(scope: DispatcherScope, browser: Browser, selectors: Selectors) {
|
||||
super(scope, browser);
|
||||
|
@ -22,7 +22,7 @@ import path from 'path';
|
||||
import program from 'commander';
|
||||
import os from 'os';
|
||||
import fs from 'fs';
|
||||
import { runServer, printApiJson, launchBrowserServer, installBrowsers } from './driver';
|
||||
import { runDriver, runServer, printApiJson, launchBrowserServer, installBrowsers } from './driver';
|
||||
import { showTraceViewer } from '../server/trace/viewer/traceViewer';
|
||||
import * as playwright from '../..';
|
||||
import { BrowserContext } from '../client/browserContext';
|
||||
@ -176,7 +176,9 @@ if (process.env.PWTRACE) {
|
||||
}
|
||||
|
||||
if (process.argv[2] === 'run-driver')
|
||||
runServer();
|
||||
runDriver();
|
||||
else if (process.argv[2] === 'run-server')
|
||||
runServer(process.argv[3] ? +process.argv[3] : undefined);
|
||||
else if (process.argv[2] === 'print-api-json')
|
||||
printApiJson();
|
||||
else if (process.argv[2] === 'launch-server')
|
||||
|
@ -25,6 +25,7 @@ import { DispatcherConnection } from '../dispatchers/dispatcher';
|
||||
import { PlaywrightDispatcher } from '../dispatchers/playwrightDispatcher';
|
||||
import { installBrowsersWithProgressBar } from '../install/installer';
|
||||
import { Transport } from '../protocol/transport';
|
||||
import { PlaywrightServer } from '../remote/playwrightServer';
|
||||
import { createPlaywright } from '../server/playwright';
|
||||
import { gracefullyCloseAll } from '../server/processLauncher';
|
||||
import { BrowserName } from '../utils/registry';
|
||||
@ -37,7 +38,7 @@ export function printProtocol() {
|
||||
console.log(fs.readFileSync(path.join(__dirname, '..', '..', 'protocol.yml'), 'utf8'));
|
||||
}
|
||||
|
||||
export function runServer() {
|
||||
export function runDriver() {
|
||||
const dispatcherConnection = new DispatcherConnection();
|
||||
const transport = new Transport(process.stdout, process.stdin);
|
||||
transport.onmessage = message => dispatcherConnection.dispatch(JSON.parse(message));
|
||||
@ -56,6 +57,11 @@ export function runServer() {
|
||||
new PlaywrightDispatcher(dispatcherConnection.rootDispatcher(), playwright);
|
||||
}
|
||||
|
||||
export async function runServer(port: number | undefined) {
|
||||
const wsEndpoint = await PlaywrightServer.startDefault(port);
|
||||
console.log('Listening on ' + wsEndpoint); // eslint-disable-line no-console
|
||||
}
|
||||
|
||||
export async function launchBrowserServer(browserName: string, configFile?: string) {
|
||||
let options: LaunchServerOptions = {};
|
||||
if (configFile)
|
||||
|
@ -20,19 +20,16 @@ import { BrowserContext, prepareBrowserContextParams } from './browserContext';
|
||||
import { ChannelOwner } from './channelOwner';
|
||||
import { LaunchOptions, LaunchServerOptions, ConnectOptions, LaunchPersistentContextOptions } from './types';
|
||||
import WebSocket from 'ws';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import { Connection } from './connection';
|
||||
import { serializeError } from '../protocol/serializers';
|
||||
import { Events } from './events';
|
||||
import { TimeoutSettings } from '../utils/timeoutSettings';
|
||||
import { ChildProcess } from 'child_process';
|
||||
import { envObjectToArray } from './clientHelper';
|
||||
import { assert, makeWaitForNextTask, mkdirIfNeeded } from '../utils/utils';
|
||||
import { SelectorsOwner, sharedSelectors } from './selectors';
|
||||
import { assert, makeWaitForNextTask } from '../utils/utils';
|
||||
import { kBrowserClosedError } from '../utils/errors';
|
||||
import { Stream } from './stream';
|
||||
import * as api from '../../types/types';
|
||||
import type { Playwright } from './playwright';
|
||||
|
||||
export interface BrowserServerLauncher {
|
||||
launchServer(options?: LaunchServerOptions): Promise<api.BrowserServer>;
|
||||
@ -152,13 +149,15 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel, chann
|
||||
reject(new Error('Server disconnected: ' + event.reason));
|
||||
};
|
||||
ws.addEventListener('close', prematureCloseListener);
|
||||
const remoteBrowser = await connection.waitForObjectWithKnownName('remoteBrowser') as RemoteBrowser;
|
||||
const playwright = await connection.waitForObjectWithKnownName('Playwright') as Playwright;
|
||||
|
||||
// Inherit shared selectors for connected browser.
|
||||
const selectorsOwner = SelectorsOwner.from(remoteBrowser._initializer.selectors);
|
||||
sharedSelectors._addChannel(selectorsOwner);
|
||||
if (!playwright._initializer.preLaunchedBrowser) {
|
||||
reject(new Error('Malformed endpoint. Did you use launchServer method?'));
|
||||
ws.close();
|
||||
return;
|
||||
}
|
||||
|
||||
const browser = Browser.from(remoteBrowser._initializer.browser);
|
||||
const browser = Browser.from(playwright._initializer.preLaunchedBrowser!);
|
||||
browser._logger = logger;
|
||||
browser._isRemote = true;
|
||||
const closeListener = () => {
|
||||
@ -173,7 +172,7 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel, chann
|
||||
ws.removeEventListener('close', prematureCloseListener);
|
||||
ws.addEventListener('close', closeListener);
|
||||
browser.on(Events.Browser.Disconnected, () => {
|
||||
sharedSelectors._removeChannel(selectorsOwner);
|
||||
playwright._cleanup();
|
||||
ws.removeEventListener('close', closeListener);
|
||||
ws.close();
|
||||
});
|
||||
@ -209,16 +208,3 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel, chann
|
||||
}, logger);
|
||||
}
|
||||
}
|
||||
|
||||
export class RemoteBrowser extends ChannelOwner<channels.RemoteBrowserChannel, channels.RemoteBrowserInitializer> {
|
||||
constructor(parent: ChannelOwner, type: string, guid: string, initializer: channels.RemoteBrowserInitializer) {
|
||||
super(parent, type, guid, initializer);
|
||||
this._channel.on('video', ({ context, stream, relativePath }) => this._onVideo(BrowserContext.from(context), Stream.from(stream), relativePath));
|
||||
}
|
||||
|
||||
private async _onVideo(context: BrowserContext, stream: Stream, relativePath: string) {
|
||||
const videoFile = path.join(context._options.recordVideo!.dir, relativePath);
|
||||
await mkdirIfNeeded(videoFile);
|
||||
stream.stream().pipe(fs.createWriteStream(videoFile));
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@
|
||||
|
||||
import { Browser } from './browser';
|
||||
import { BrowserContext } from './browserContext';
|
||||
import { BrowserType, RemoteBrowser } from './browserType';
|
||||
import { BrowserType } from './browserType';
|
||||
import { ChannelOwner } from './channelOwner';
|
||||
import { ElementHandle } from './elementHandle';
|
||||
import { Frame } from './frame';
|
||||
@ -197,9 +197,6 @@ export class Connection {
|
||||
case 'Playwright':
|
||||
result = new Playwright(parent, type, guid, initializer);
|
||||
break;
|
||||
case 'RemoteBrowser':
|
||||
result = new RemoteBrowser(parent, type, guid, initializer);
|
||||
break;
|
||||
case 'Request':
|
||||
result = new Request(parent, type, guid, initializer);
|
||||
break;
|
||||
|
@ -42,6 +42,7 @@ export class Playwright extends ChannelOwner<channels.PlaywrightChannel, channel
|
||||
readonly devices: Devices;
|
||||
readonly selectors: Selectors;
|
||||
readonly errors: { TimeoutError: typeof TimeoutError };
|
||||
private _selectorsOwner: SelectorsOwner;
|
||||
|
||||
constructor(parent: ChannelOwner, type: string, guid: string, initializer: channels.PlaywrightInitializer) {
|
||||
super(parent, type, guid, initializer);
|
||||
@ -55,6 +56,12 @@ export class Playwright extends ChannelOwner<channels.PlaywrightChannel, channel
|
||||
this.devices[name] = descriptor;
|
||||
this.selectors = sharedSelectors;
|
||||
this.errors = { TimeoutError };
|
||||
this.selectors._addChannel(SelectorsOwner.from(initializer.selectors));
|
||||
|
||||
this._selectorsOwner = SelectorsOwner.from(initializer.selectors);
|
||||
this.selectors._addChannel(this._selectorsOwner);
|
||||
}
|
||||
|
||||
_cleanup() {
|
||||
this.selectors._removeChannel(this._selectorsOwner);
|
||||
}
|
||||
}
|
||||
|
@ -21,10 +21,11 @@ import { BrowserTypeDispatcher } from './browserTypeDispatcher';
|
||||
import { Dispatcher, DispatcherScope } from './dispatcher';
|
||||
import { ElectronDispatcher } from './electronDispatcher';
|
||||
import { SelectorsDispatcher } from './selectorsDispatcher';
|
||||
import type { BrowserDispatcher } from './browserDispatcher';
|
||||
import * as types from '../server/types';
|
||||
|
||||
export class PlaywrightDispatcher extends Dispatcher<Playwright, channels.PlaywrightInitializer> implements channels.PlaywrightChannel {
|
||||
constructor(scope: DispatcherScope, playwright: Playwright) {
|
||||
constructor(scope: DispatcherScope, playwright: Playwright, customSelectors?: SelectorsDispatcher, preLaunchedBrowser?: BrowserDispatcher) {
|
||||
const descriptors = require('../server/deviceDescriptors') as types.Devices;
|
||||
const deviceDescriptors = Object.entries(descriptors)
|
||||
.map(([name, descriptor]) => ({ name, descriptor }));
|
||||
@ -35,7 +36,8 @@ export class PlaywrightDispatcher extends Dispatcher<Playwright, channels.Playwr
|
||||
android: new AndroidDispatcher(scope, playwright.android),
|
||||
electron: new ElectronDispatcher(scope, playwright.electron),
|
||||
deviceDescriptors,
|
||||
selectors: new SelectorsDispatcher(scope, playwright.selectors),
|
||||
selectors: customSelectors || new SelectorsDispatcher(scope, playwright.selectors),
|
||||
preLaunchedBrowser,
|
||||
}, false, 'Playwright');
|
||||
}
|
||||
}
|
||||
|
@ -34,9 +34,9 @@ function setupInProcess(): PlaywrightAPI {
|
||||
// Initialize Playwright channel.
|
||||
new PlaywrightDispatcher(dispatcherConnection.rootDispatcher(), 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);
|
||||
playwrightAPI.chromium._serverLauncher = new BrowserServerLauncherImpl(playwright, playwright.chromium);
|
||||
playwrightAPI.firefox._serverLauncher = new BrowserServerLauncherImpl(playwright, playwright.firefox);
|
||||
playwrightAPI.webkit._serverLauncher = new BrowserServerLauncherImpl(playwright, playwright.webkit);
|
||||
|
||||
// Switch to async dispatch after we got Playwright object.
|
||||
dispatcherConnection.onmessage = message => setImmediate(() => clientConnection.dispatch(message));
|
||||
|
@ -177,18 +177,11 @@ export type PlaywrightInitializer = {
|
||||
},
|
||||
}[],
|
||||
selectors: SelectorsChannel,
|
||||
preLaunchedBrowser?: BrowserChannel,
|
||||
};
|
||||
export interface PlaywrightChannel extends Channel {
|
||||
}
|
||||
|
||||
// ----------- RemoteBrowser -----------
|
||||
export type RemoteBrowserInitializer = {
|
||||
browser: BrowserChannel,
|
||||
selectors: SelectorsChannel,
|
||||
};
|
||||
export interface RemoteBrowserChannel extends Channel {
|
||||
}
|
||||
|
||||
// ----------- Selectors -----------
|
||||
export type SelectorsInitializer = {};
|
||||
export interface SelectorsChannel extends Channel {
|
||||
|
@ -371,14 +371,8 @@ Playwright:
|
||||
- firefox
|
||||
- webkit
|
||||
selectors: Selectors
|
||||
|
||||
|
||||
RemoteBrowser:
|
||||
type: interface
|
||||
|
||||
initializer:
|
||||
browser: Browser
|
||||
selectors: Selectors
|
||||
# Only present when connecting remotely via BrowserType.connect() method.
|
||||
preLaunchedBrowser: Browser?
|
||||
|
||||
|
||||
Selectors:
|
||||
|
@ -16,47 +16,99 @@
|
||||
|
||||
import debug from 'debug';
|
||||
import * as http from 'http';
|
||||
import WebSocket from 'ws';
|
||||
import { DispatcherConnection } from '../dispatchers/dispatcher';
|
||||
import * as ws from 'ws';
|
||||
import { DispatcherConnection, DispatcherScope } from '../dispatchers/dispatcher';
|
||||
import { PlaywrightDispatcher } from '../dispatchers/playwrightDispatcher';
|
||||
import { createPlaywright } from '../server/playwright';
|
||||
import { gracefullyCloseAll } from '../server/processLauncher';
|
||||
|
||||
const debugLog = debug('pw:server');
|
||||
|
||||
export interface PlaywrightServerDelegate {
|
||||
path: string;
|
||||
allowMultipleClients: boolean;
|
||||
onConnect(rootScope: DispatcherScope): () => any;
|
||||
onClose: () => any;
|
||||
}
|
||||
|
||||
export class PlaywrightServer {
|
||||
private _server: http.Server | undefined;
|
||||
private _client: WebSocket | undefined;
|
||||
private _clientsCount = 0;
|
||||
private _delegate: PlaywrightServerDelegate;
|
||||
|
||||
listen(port: number) {
|
||||
this._server = http.createServer((request, response) => {
|
||||
static async startDefault(port: number = 0): Promise<string> {
|
||||
const delegate: PlaywrightServerDelegate = {
|
||||
path: '/ws',
|
||||
allowMultipleClients: false,
|
||||
onClose: gracefullyCloseAll,
|
||||
onConnect: (rootScope: DispatcherScope) => {
|
||||
new PlaywrightDispatcher(rootScope, createPlaywright());
|
||||
return () => gracefullyCloseAll().catch(e => {});
|
||||
},
|
||||
};
|
||||
const server = new PlaywrightServer(delegate);
|
||||
return server.listen(port);
|
||||
}
|
||||
|
||||
constructor(delegate: PlaywrightServerDelegate) {
|
||||
this._delegate = delegate;
|
||||
}
|
||||
|
||||
async listen(port: number = 0): Promise<string> {
|
||||
const server = http.createServer((request, response) => {
|
||||
response.end('Running');
|
||||
});
|
||||
this._server.on('error', error => debugLog(error));
|
||||
this._server.listen(port);
|
||||
debugLog('Listening on ' + port);
|
||||
server.on('error', error => debugLog(error));
|
||||
|
||||
const wsServer = new WebSocket.Server({ server: this._server, path: '/ws' });
|
||||
wsServer.on('connection', async ws => {
|
||||
if (this._client) {
|
||||
ws.close();
|
||||
const path = this._delegate.path;
|
||||
const wsEndpoint = await new Promise<string>(resolve => {
|
||||
server.listen(port, () => {
|
||||
const address = server.address();
|
||||
const wsEndpoint = typeof address === 'string' ? `${address}${path}` : `ws://127.0.0.1:${address.port}${path}`;
|
||||
resolve(wsEndpoint);
|
||||
});
|
||||
});
|
||||
|
||||
this._server = server;
|
||||
debugLog('Listening at ' + wsEndpoint);
|
||||
|
||||
const wsServer = new ws.Server({ server: this._server, path });
|
||||
wsServer.on('connection', async socket => {
|
||||
if (this._clientsCount && !this._delegate.allowMultipleClients) {
|
||||
socket.close();
|
||||
return;
|
||||
}
|
||||
this._client = ws;
|
||||
this._clientsCount++;
|
||||
debugLog('Incoming connection');
|
||||
const dispatcherConnection = new DispatcherConnection();
|
||||
ws.on('message', message => dispatcherConnection.dispatch(JSON.parse(message.toString())));
|
||||
ws.on('close', () => {
|
||||
|
||||
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()));
|
||||
});
|
||||
|
||||
const scope = connection.rootDispatcher();
|
||||
const onDisconnect = this._delegate.onConnect(scope);
|
||||
const disconnect = () => {
|
||||
this._clientsCount--;
|
||||
// Avoid sending any more messages over closed socket.
|
||||
connection.onmessage = () => {};
|
||||
onDisconnect();
|
||||
};
|
||||
socket.on('close', () => {
|
||||
debugLog('Client closed');
|
||||
this._onDisconnect().catch(debugLog);
|
||||
disconnect();
|
||||
});
|
||||
ws.on('error', error => {
|
||||
socket.on('error', error => {
|
||||
debugLog('Client error ' + error);
|
||||
this._onDisconnect().catch(debugLog);
|
||||
disconnect();
|
||||
});
|
||||
dispatcherConnection.onmessage = message => ws.send(JSON.stringify(message));
|
||||
new PlaywrightDispatcher(dispatcherConnection.rootDispatcher(), createPlaywright());
|
||||
});
|
||||
|
||||
return wsEndpoint;
|
||||
}
|
||||
|
||||
async close() {
|
||||
@ -64,11 +116,6 @@ export class PlaywrightServer {
|
||||
return;
|
||||
debugLog('Closing server');
|
||||
await new Promise(f => this._server!.close(f));
|
||||
await gracefullyCloseAll();
|
||||
}
|
||||
|
||||
private async _onDisconnect() {
|
||||
await gracefullyCloseAll();
|
||||
this._client = undefined;
|
||||
await this._delegate.onClose();
|
||||
}
|
||||
}
|
||||
|
@ -1,21 +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 { PlaywrightServer } from './remote/playwrightServer';
|
||||
|
||||
const server = new PlaywrightServer();
|
||||
server.listen(+process.argv[2]);
|
||||
console.log('Listening on ' + process.argv[2]); // eslint-disable-line no-console
|
@ -58,7 +58,7 @@ class ServiceMode {
|
||||
|
||||
async setup(workerInfo: WorkerInfo) {
|
||||
const port = 10507 + workerInfo.workerIndex;
|
||||
this._serviceProcess = childProcess.fork(path.join(__dirname, '..', '..', 'lib', 'service.js'), [String(port)], {
|
||||
this._serviceProcess = childProcess.fork(path.join(__dirname, '..', '..', 'lib', 'cli', 'cli.js'), ['run-server', String(port)], {
|
||||
stdio: 'pipe'
|
||||
});
|
||||
this._serviceProcess.stderr.pipe(process.stderr);
|
||||
|
@ -149,7 +149,6 @@ DEPS['src/web/traceViewer/'] = ['src/common/', 'src/web/'];
|
||||
DEPS['src/web/traceViewer/ui/'] = ['src/common/', 'src/web/traceViewer/', 'src/web/', 'src/server/trace/viewer/', 'src/server/trace/', 'src/server/trace/common/', 'src/server/snapshot/snapshotTypes.ts'];
|
||||
// The service is a cross-cutting feature, and so it depends on a bunch of things.
|
||||
DEPS['src/remote/'] = ['src/client/', 'src/debug/', 'src/dispatchers/', 'src/server/', 'src/server/supplements/', 'src/server/electron/', 'src/server/trace/'];
|
||||
DEPS['src/service.ts'] = ['src/remote/'];
|
||||
|
||||
// CLI should only use client-side features.
|
||||
DEPS['src/cli/'] = ['src/cli/**', 'src/client/**', 'src/install/**', 'src/generated/', 'src/server/injected/', 'src/debug/injected/', 'src/server/trace/**', 'src/utils/**'];
|
||||
|
Loading…
Reference in New Issue
Block a user