feat: make browserServer.kill() wait for the process to exit (#2375)
This ensures we cleaned everything up.
This commit is contained in:
parent
9dfe9348ac
commit
057ae14adc
|
|
@ -3941,8 +3941,9 @@ Emitted when the browser server closes.
|
||||||
Closes the browser gracefully and makes sure the process is terminated.
|
Closes the browser gracefully and makes sure the process is terminated.
|
||||||
|
|
||||||
#### browserServer.kill()
|
#### browserServer.kill()
|
||||||
|
- returns: <[Promise]>
|
||||||
|
|
||||||
Kills the browser process.
|
Kills the browser process and waits for the process to exit.
|
||||||
|
|
||||||
#### browserServer.process()
|
#### browserServer.process()
|
||||||
- returns: <[ChildProcess]> Spawned browser application process.
|
- returns: <[ChildProcess]> Spawned browser application process.
|
||||||
|
|
|
||||||
|
|
@ -50,13 +50,15 @@ export class WebSocketWrapper {
|
||||||
|
|
||||||
export class BrowserServer extends EventEmitter {
|
export class BrowserServer extends EventEmitter {
|
||||||
private _process: ChildProcess;
|
private _process: ChildProcess;
|
||||||
private _gracefullyClose: (() => Promise<void>);
|
private _gracefullyClose: () => Promise<void>;
|
||||||
|
private _kill: () => Promise<void>;
|
||||||
_webSocketWrapper: WebSocketWrapper | null = null;
|
_webSocketWrapper: WebSocketWrapper | null = null;
|
||||||
|
|
||||||
constructor(process: ChildProcess, gracefullyClose: () => Promise<void>) {
|
constructor(process: ChildProcess, gracefullyClose: () => Promise<void>, kill: () => Promise<void>) {
|
||||||
super();
|
super();
|
||||||
this._process = process;
|
this._process = process;
|
||||||
this._gracefullyClose = gracefullyClose;
|
this._gracefullyClose = gracefullyClose;
|
||||||
|
this._kill = kill;
|
||||||
}
|
}
|
||||||
|
|
||||||
process(): ChildProcess {
|
process(): ChildProcess {
|
||||||
|
|
@ -67,8 +69,8 @@ export class BrowserServer extends EventEmitter {
|
||||||
return this._webSocketWrapper ? this._webSocketWrapper.wsEndpoint : '';
|
return this._webSocketWrapper ? this._webSocketWrapper.wsEndpoint : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
kill() {
|
async kill(): Promise<void> {
|
||||||
helper.killProcess(this._process);
|
await this._kill();
|
||||||
}
|
}
|
||||||
|
|
||||||
async close(): Promise<void> {
|
async close(): Promise<void> {
|
||||||
|
|
@ -84,7 +86,7 @@ export class BrowserServer extends EventEmitter {
|
||||||
try {
|
try {
|
||||||
await helper.waitWithDeadline(this.close(), '', deadline, ''); // The error message is ignored.
|
await helper.waitWithDeadline(this.close(), '', deadline, ''); // The error message is ignored.
|
||||||
} catch (ignored) {
|
} catch (ignored) {
|
||||||
this.kill();
|
await this.kill(); // Make sure to await actual process exit.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -230,7 +230,7 @@ export abstract class BrowserTypeBase implements BrowserType {
|
||||||
// "Cannot access 'browserServer' before initialization" if something went wrong.
|
// "Cannot access 'browserServer' before initialization" if something went wrong.
|
||||||
let transport: ConnectionTransport | undefined = undefined;
|
let transport: ConnectionTransport | undefined = undefined;
|
||||||
let browserServer: BrowserServer | undefined = undefined;
|
let browserServer: BrowserServer | undefined = undefined;
|
||||||
const { launchedProcess, gracefullyClose } = await launchProcess({
|
const { launchedProcess, gracefullyClose, kill } = await launchProcess({
|
||||||
executablePath: executable,
|
executablePath: executable,
|
||||||
args: browserArguments,
|
args: browserArguments,
|
||||||
env: this._amendEnvironment(env, userDataDir, executable, browserArguments),
|
env: this._amendEnvironment(env, userDataDir, executable, browserArguments),
|
||||||
|
|
@ -248,7 +248,7 @@ export abstract class BrowserTypeBase implements BrowserType {
|
||||||
// our connection ignores kBrowserCloseMessageId.
|
// our connection ignores kBrowserCloseMessageId.
|
||||||
this._attemptToGracefullyCloseBrowser(transport!);
|
this._attemptToGracefullyCloseBrowser(transport!);
|
||||||
},
|
},
|
||||||
onkill: (exitCode, signal) => {
|
onExit: (exitCode, signal) => {
|
||||||
if (browserServer)
|
if (browserServer)
|
||||||
browserServer.emit(Events.BrowserServer.Close, exitCode, signal);
|
browserServer.emit(Events.BrowserServer.Close, exitCode, signal);
|
||||||
},
|
},
|
||||||
|
|
@ -269,7 +269,7 @@ export abstract class BrowserTypeBase implements BrowserType {
|
||||||
helper.killProcess(launchedProcess);
|
helper.killProcess(launchedProcess);
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
browserServer = new BrowserServer(launchedProcess, gracefullyClose);
|
browserServer = new BrowserServer(launchedProcess, gracefullyClose, kill);
|
||||||
return { browserServer, downloadsPath, transport };
|
return { browserServer, downloadsPath, transport };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -173,7 +173,7 @@ export class Electron {
|
||||||
|
|
||||||
const logger = new RootLogger(options.logger);
|
const logger = new RootLogger(options.logger);
|
||||||
const electronArguments = ['--inspect=0', '--remote-debugging-port=0', '--require', path.join(__dirname, 'electronLoader.js'), ...args];
|
const electronArguments = ['--inspect=0', '--remote-debugging-port=0', '--require', path.join(__dirname, 'electronLoader.js'), ...args];
|
||||||
const { launchedProcess, gracefullyClose } = await launchProcess({
|
const { launchedProcess, gracefullyClose, kill } = await launchProcess({
|
||||||
executablePath,
|
executablePath,
|
||||||
args: electronArguments,
|
args: electronArguments,
|
||||||
env,
|
env,
|
||||||
|
|
@ -185,7 +185,7 @@ export class Electron {
|
||||||
cwd: options.cwd,
|
cwd: options.cwd,
|
||||||
tempDirectories: [],
|
tempDirectories: [],
|
||||||
attemptToGracefullyClose: () => app!.close(),
|
attemptToGracefullyClose: () => app!.close(),
|
||||||
onkill: (exitCode, signal) => {
|
onExit: (exitCode, signal) => {
|
||||||
if (app)
|
if (app)
|
||||||
app.emit(ElectronEvents.ElectronApplication.Close, exitCode, signal);
|
app.emit(ElectronEvents.ElectronApplication.Close, exitCode, signal);
|
||||||
},
|
},
|
||||||
|
|
@ -198,7 +198,7 @@ export class Electron {
|
||||||
|
|
||||||
const chromeMatch = await waitForLine(launchedProcess, launchedProcess.stderr, /^DevTools listening on (ws:\/\/.*)$/, helper.timeUntilDeadline(deadline), timeoutError);
|
const chromeMatch = await waitForLine(launchedProcess, launchedProcess.stderr, /^DevTools listening on (ws:\/\/.*)$/, helper.timeUntilDeadline(deadline), timeoutError);
|
||||||
const chromeTransport = await WebSocketTransport.connect(chromeMatch[1], logger, deadline);
|
const chromeTransport = await WebSocketTransport.connect(chromeMatch[1], logger, deadline);
|
||||||
const browserServer = new BrowserServer(launchedProcess, gracefullyClose);
|
const browserServer = new BrowserServer(launchedProcess, gracefullyClose, kill);
|
||||||
const browser = await CRBrowser.connect(chromeTransport, { headful: true, logger, persistent: { viewport: null }, ownedServer: browserServer });
|
const browser = await CRBrowser.connect(chromeTransport, { headful: true, logger, persistent: { viewport: null }, ownedServer: browserServer });
|
||||||
app = new ElectronApplication(logger, browser, nodeConnection);
|
app = new ElectronApplication(logger, browser, nodeConnection);
|
||||||
await app._init();
|
await app._init();
|
||||||
|
|
|
||||||
|
|
@ -56,13 +56,14 @@ export type LaunchProcessOptions = {
|
||||||
|
|
||||||
// Note: attemptToGracefullyClose should reject if it does not close the browser.
|
// Note: attemptToGracefullyClose should reject if it does not close the browser.
|
||||||
attemptToGracefullyClose: () => Promise<any>,
|
attemptToGracefullyClose: () => Promise<any>,
|
||||||
onkill: (exitCode: number | null, signal: string | null) => void,
|
onExit: (exitCode: number | null, signal: string | null) => void,
|
||||||
logger: RootLogger,
|
logger: RootLogger,
|
||||||
};
|
};
|
||||||
|
|
||||||
type LaunchResult = {
|
type LaunchResult = {
|
||||||
launchedProcess: childProcess.ChildProcess,
|
launchedProcess: childProcess.ChildProcess,
|
||||||
gracefullyClose: () => Promise<void>,
|
gracefullyClose: () => Promise<void>,
|
||||||
|
kill: () => Promise<void>,
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function launchProcess(options: LaunchProcessOptions): Promise<LaunchResult> {
|
export async function launchProcess(options: LaunchProcessOptions): Promise<LaunchResult> {
|
||||||
|
|
@ -110,14 +111,14 @@ export async function launchProcess(options: LaunchProcessOptions): Promise<Laun
|
||||||
|
|
||||||
let processClosed = false;
|
let processClosed = false;
|
||||||
let fulfillClose = () => {};
|
let fulfillClose = () => {};
|
||||||
const waitForClose = new Promise(f => fulfillClose = f);
|
const waitForClose = new Promise<void>(f => fulfillClose = f);
|
||||||
let fulfillCleanup = () => {};
|
let fulfillCleanup = () => {};
|
||||||
const waitForCleanup = new Promise(f => fulfillCleanup = f);
|
const waitForCleanup = new Promise<void>(f => fulfillCleanup = f);
|
||||||
spawnedProcess.once('exit', (exitCode, signal) => {
|
spawnedProcess.once('exit', (exitCode, signal) => {
|
||||||
logger._log(browserLog, `<process did exit ${exitCode}, ${signal}>`);
|
logger._log(browserLog, `<process did exit ${exitCode}, ${signal}>`);
|
||||||
processClosed = true;
|
processClosed = true;
|
||||||
helper.removeEventListeners(listeners);
|
helper.removeEventListeners(listeners);
|
||||||
options.onkill(exitCode, signal);
|
options.onExit(exitCode, signal);
|
||||||
fulfillClose();
|
fulfillClose();
|
||||||
// Cleanup as process exits.
|
// Cleanup as process exits.
|
||||||
cleanup().then(fulfillCleanup);
|
cleanup().then(fulfillCleanup);
|
||||||
|
|
@ -175,7 +176,12 @@ export async function launchProcess(options: LaunchProcessOptions): Promise<Laun
|
||||||
} catch (e) { }
|
} catch (e) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
return { launchedProcess: spawnedProcess, gracefullyClose };
|
function killAndWait() {
|
||||||
|
killProcess();
|
||||||
|
return waitForCleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { launchedProcess: spawnedProcess, gracefullyClose, kill: killAndWait };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function waitForLine(process: childProcess.ChildProcess, inputStream: stream.Readable, regex: RegExp, timeout: number, timeoutError: TimeoutError): Promise<RegExpMatchArray> {
|
export function waitForLine(process: childProcess.ChildProcess, inputStream: stream.Readable, regex: RegExp, timeout: number, timeoutError: TimeoutError): Promise<RegExpMatchArray> {
|
||||||
|
|
|
||||||
|
|
@ -223,8 +223,8 @@ describe('Browser.close', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('browserType.launch |webSocket| option', function() {
|
describe('browserType.launchServer', function() {
|
||||||
it('should support the webSocket option', async({browserType, defaultBrowserOptions}) => {
|
it('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();
|
||||||
|
|
@ -237,7 +237,7 @@ describe('browserType.launch |webSocket| option', function() {
|
||||||
await browserServer._checkLeaks();
|
await browserServer._checkLeaks();
|
||||||
await browserServer.close();
|
await browserServer.close();
|
||||||
});
|
});
|
||||||
it('should fire "disconnected" when closing with webSocket', async({browserType, defaultBrowserOptions}) => {
|
it('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));
|
||||||
|
|
@ -248,6 +248,19 @@ describe('browserType.launch |webSocket| option', function() {
|
||||||
closedPromise,
|
closedPromise,
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
it('should fire "close" event during kill', async({browserType, defaultBrowserOptions}) => {
|
||||||
|
const order = [];
|
||||||
|
const browserServer = await browserType.launchServer(defaultBrowserOptions);
|
||||||
|
const closedPromise = new Promise(f => browserServer.on('close', () => {
|
||||||
|
order.push('closed');
|
||||||
|
f();
|
||||||
|
}));
|
||||||
|
await Promise.all([
|
||||||
|
browserServer.kill().then(() => order.push('killed')),
|
||||||
|
closedPromise,
|
||||||
|
]);
|
||||||
|
expect(order).toEqual(['closed', 'killed']);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('browserType.connect', function() {
|
describe('browserType.connect', function() {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue