chore: use ipc transport for out-of-process driver (#11826)
This commit is contained in:
parent
fdda759a9d
commit
1215057ca1
|
|
@ -22,7 +22,7 @@ import { BrowserType } from '../client/browserType';
|
|||
import { LaunchServerOptions } from '../client/types';
|
||||
import { DispatcherConnection, Root } from '../dispatchers/dispatcher';
|
||||
import { PlaywrightDispatcher } from '../dispatchers/playwrightDispatcher';
|
||||
import { Transport } from '../protocol/transport';
|
||||
import { IpcTransport, PipeTransport } from '../protocol/transport';
|
||||
import { PlaywrightServer } from '../remote/playwrightServer';
|
||||
import { createPlaywright } from '../server/playwright';
|
||||
import { gracefullyCloseAll } from '../utils/processLauncher';
|
||||
|
|
@ -38,7 +38,7 @@ export function runDriver() {
|
|||
const playwright = createPlaywright(sdkLanguage);
|
||||
return new PlaywrightDispatcher(rootScope, playwright);
|
||||
});
|
||||
const transport = new Transport(process.stdout, process.stdin);
|
||||
const transport = process.send ? new IpcTransport(process) : new PipeTransport(process.stdout, process.stdin);
|
||||
transport.onmessage = message => dispatcherConnection.dispatch(JSON.parse(message));
|
||||
dispatcherConnection.onmessage = message => transport.send(JSON.stringify(message));
|
||||
transport.onclose = async () => {
|
||||
|
|
|
|||
|
|
@ -15,10 +15,11 @@
|
|||
*/
|
||||
|
||||
import { Connection } from './client/connection';
|
||||
import { Transport } from './protocol/transport';
|
||||
import { IpcTransport } from './protocol/transport';
|
||||
import { Playwright } from './client/playwright';
|
||||
import * as childProcess from 'child_process';
|
||||
import * as path from 'path';
|
||||
import { ManualPromise } from './utils/async';
|
||||
|
||||
export async function start(env: any = {}): Promise<{ playwright: Playwright, stop: () => Promise<void> }> {
|
||||
const client = new PlaywrightClient(env);
|
||||
|
|
@ -30,16 +31,13 @@ export async function start(env: any = {}): Promise<{ playwright: Playwright, st
|
|||
class PlaywrightClient {
|
||||
_playwright: Promise<Playwright>;
|
||||
_driverProcess: childProcess.ChildProcess;
|
||||
private _closePromise: Promise<void>;
|
||||
private _onExit: (exitCode: number | null, signal: string | null) => {};
|
||||
private _closePromise = new ManualPromise<void>();
|
||||
private _transport: IpcTransport;
|
||||
private _stopped = false;
|
||||
|
||||
constructor(env: any) {
|
||||
this._onExit = (exitCode: number | null, signal: string | null) => {
|
||||
throw new Error(`Server closed with exitCode=${exitCode} signal=${signal}`);
|
||||
};
|
||||
|
||||
this._driverProcess = childProcess.fork(path.join(__dirname, 'cli', 'cli.js'), ['run-driver'], {
|
||||
stdio: 'pipe',
|
||||
stdio: ['inherit', 'inherit', 'inherit', 'ipc'],
|
||||
detached: true,
|
||||
env: {
|
||||
...process.env,
|
||||
|
|
@ -47,22 +45,28 @@ class PlaywrightClient {
|
|||
},
|
||||
});
|
||||
this._driverProcess.unref();
|
||||
this._driverProcess.on('exit', this._onExit);
|
||||
this._driverProcess.on('exit', this._onExit.bind(this));
|
||||
|
||||
const connection = new Connection();
|
||||
const transport = new Transport(this._driverProcess.stdin!, this._driverProcess.stdout!);
|
||||
connection.onmessage = message => transport.send(JSON.stringify(message));
|
||||
transport.onmessage = message => connection.dispatch(JSON.parse(message));
|
||||
this._closePromise = new Promise(f => transport.onclose = f);
|
||||
this._transport = new IpcTransport(this._driverProcess);
|
||||
connection.onmessage = message => this._transport.send(JSON.stringify(message));
|
||||
this._transport.onmessage = message => connection.dispatch(JSON.parse(message));
|
||||
this._transport.onclose = () => this._closePromise.resolve();
|
||||
|
||||
this._playwright = connection.initializePlaywright();
|
||||
}
|
||||
|
||||
async stop() {
|
||||
this._driverProcess.removeListener('exit', this._onExit);
|
||||
this._driverProcess.stdin!.destroy();
|
||||
this._driverProcess.stdout!.destroy();
|
||||
this._driverProcess.stderr!.destroy();
|
||||
this._stopped = true;
|
||||
this._transport.close();
|
||||
await this._closePromise;
|
||||
}
|
||||
|
||||
private _onExit(exitCode: number | null, signal: string | null) {
|
||||
if (this._stopped)
|
||||
this._closePromise.resolve();
|
||||
else
|
||||
throw new Error(`Server closed with exitCode=${exitCode} signal=${signal}`);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ChildProcess } from 'child_process';
|
||||
import { makeWaitForNextTask } from '../utils/utils';
|
||||
|
||||
export interface WritableStream {
|
||||
|
|
@ -29,7 +30,7 @@ export interface ClosableStream {
|
|||
close(): void;
|
||||
}
|
||||
|
||||
export class Transport {
|
||||
export class PipeTransport {
|
||||
private _pipeWrite: WritableStream;
|
||||
private _data = Buffer.from([]);
|
||||
private _waitForNextTask = makeWaitForNextTask();
|
||||
|
|
@ -102,3 +103,27 @@ export class Transport {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class IpcTransport {
|
||||
private _process: NodeJS.Process | ChildProcess;
|
||||
onmessage?: (message: string) => void;
|
||||
onclose?: () => void;
|
||||
|
||||
constructor(process: NodeJS.Process | ChildProcess) {
|
||||
this._process = process;
|
||||
this._process.on('message', message => {
|
||||
if (message === '<eof>')
|
||||
this.onclose?.();
|
||||
else
|
||||
this.onmessage?.(message);
|
||||
});
|
||||
}
|
||||
|
||||
send(message: string) {
|
||||
this._process.send!(message);
|
||||
}
|
||||
|
||||
close() {
|
||||
this._process.send!('<eof>');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ import { BrowserContext, validateBrowserContextOptions } from '../browserContext
|
|||
import { ProgressController } from '../progress';
|
||||
import { CRBrowser } from '../chromium/crBrowser';
|
||||
import { helper } from '../helper';
|
||||
import { Transport } from '../../protocol/transport';
|
||||
import { PipeTransport } from '../../protocol/transport';
|
||||
import { RecentLogsCollector } from '../../utils/debugLogger';
|
||||
import { TimeoutSettings } from '../../utils/timeoutSettings';
|
||||
import { AndroidWebView } from '../../protocol/channels';
|
||||
|
|
@ -95,7 +95,7 @@ export class AndroidDevice extends SdkObject {
|
|||
readonly _backend: DeviceBackend;
|
||||
readonly model: string;
|
||||
readonly serial: string;
|
||||
private _driverPromise: Promise<Transport> | undefined;
|
||||
private _driverPromise: Promise<PipeTransport> | undefined;
|
||||
private _lastId = 0;
|
||||
private _callbacks = new Map<number, { fulfill: (result: any) => void, reject: (error: Error) => void }>();
|
||||
private _pollingWebViews: NodeJS.Timeout | undefined;
|
||||
|
|
@ -155,13 +155,13 @@ export class AndroidDevice extends SdkObject {
|
|||
return await this._backend.runCommand(`shell:screencap -p`);
|
||||
}
|
||||
|
||||
private async _driver(): Promise<Transport> {
|
||||
private async _driver(): Promise<PipeTransport> {
|
||||
if (!this._driverPromise)
|
||||
this._driverPromise = this._installDriver();
|
||||
return this._driverPromise;
|
||||
}
|
||||
|
||||
private async _installDriver(): Promise<Transport> {
|
||||
private async _installDriver(): Promise<PipeTransport> {
|
||||
debug('pw:android')('Stopping the old driver');
|
||||
await this.shell(`am force-stop com.microsoft.playwright.androiddriver`);
|
||||
|
||||
|
|
@ -176,7 +176,7 @@ export class AndroidDevice extends SdkObject {
|
|||
debug('pw:android')('Starting the new driver');
|
||||
this.shell('am instrument -w com.microsoft.playwright.androiddriver.test/androidx.test.runner.AndroidJUnitRunner').catch(e => debug('pw:android')(e));
|
||||
const socket = await this._waitForLocalAbstract('playwright_android_driver_socket');
|
||||
const transport = new Transport(socket, socket, socket, 'be');
|
||||
const transport = new PipeTransport(socket, socket, socket, 'be');
|
||||
transport.onmessage = message => {
|
||||
const response = JSON.parse(message);
|
||||
const { id, result, error } = response;
|
||||
|
|
|
|||
|
|
@ -181,7 +181,7 @@ DEPS['src/protocol/'] = ['src/utils/'];
|
|||
|
||||
// Client depends on chromium protocol for types.
|
||||
DEPS['src/client/'] = ['src/common/', 'src/utils/', 'src/protocol/', 'src/server/chromium/protocol.d.ts'];
|
||||
DEPS['src/outofprocess.ts'] = ['src/client/', 'src/protocol/'];
|
||||
DEPS['src/outofprocess.ts'] = ['src/client/', 'src/protocol/', 'src/utils/'];
|
||||
|
||||
DEPS['src/dispatchers/'] = ['src/common/', 'src/utils/', 'src/protocol/', 'src/server/**'];
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue