Skip to content

Commit 057ae14

Browse files
authored
feat: make browserServer.kill() wait for the process to exit (#2375)
This ensures we cleaned everything up.
1 parent 9dfe934 commit 057ae14

File tree

6 files changed

+42
-20
lines changed

6 files changed

+42
-20
lines changed

docs/api.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3941,8 +3941,9 @@ Emitted when the browser server closes.
39413941
Closes the browser gracefully and makes sure the process is terminated.
39423942

39433943
#### browserServer.kill()
3944+
- returns: <[Promise]>
39443945

3945-
Kills the browser process.
3946+
Kills the browser process and waits for the process to exit.
39463947

39473948
#### browserServer.process()
39483949
- returns: <[ChildProcess]> Spawned browser application process.

src/server/browserServer.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,15 @@ export class WebSocketWrapper {
5050

5151
export class BrowserServer extends EventEmitter {
5252
private _process: ChildProcess;
53-
private _gracefullyClose: (() => Promise<void>);
53+
private _gracefullyClose: () => Promise<void>;
54+
private _kill: () => Promise<void>;
5455
_webSocketWrapper: WebSocketWrapper | null = null;
5556

56-
constructor(process: ChildProcess, gracefullyClose: () => Promise<void>) {
57+
constructor(process: ChildProcess, gracefullyClose: () => Promise<void>, kill: () => Promise<void>) {
5758
super();
5859
this._process = process;
5960
this._gracefullyClose = gracefullyClose;
61+
this._kill = kill;
6062
}
6163

6264
process(): ChildProcess {
@@ -67,8 +69,8 @@ export class BrowserServer extends EventEmitter {
6769
return this._webSocketWrapper ? this._webSocketWrapper.wsEndpoint : '';
6870
}
6971

70-
kill() {
71-
helper.killProcess(this._process);
72+
async kill(): Promise<void> {
73+
await this._kill();
7274
}
7375

7476
async close(): Promise<void> {
@@ -84,7 +86,7 @@ export class BrowserServer extends EventEmitter {
8486
try {
8587
await helper.waitWithDeadline(this.close(), '', deadline, ''); // The error message is ignored.
8688
} catch (ignored) {
87-
this.kill();
89+
await this.kill(); // Make sure to await actual process exit.
8890
}
8991
}
9092
}

src/server/browserType.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ export abstract class BrowserTypeBase implements BrowserType {
230230
// "Cannot access 'browserServer' before initialization" if something went wrong.
231231
let transport: ConnectionTransport | undefined = undefined;
232232
let browserServer: BrowserServer | undefined = undefined;
233-
const { launchedProcess, gracefullyClose } = await launchProcess({
233+
const { launchedProcess, gracefullyClose, kill } = await launchProcess({
234234
executablePath: executable,
235235
args: browserArguments,
236236
env: this._amendEnvironment(env, userDataDir, executable, browserArguments),
@@ -248,7 +248,7 @@ export abstract class BrowserTypeBase implements BrowserType {
248248
// our connection ignores kBrowserCloseMessageId.
249249
this._attemptToGracefullyCloseBrowser(transport!);
250250
},
251-
onkill: (exitCode, signal) => {
251+
onExit: (exitCode, signal) => {
252252
if (browserServer)
253253
browserServer.emit(Events.BrowserServer.Close, exitCode, signal);
254254
},
@@ -269,7 +269,7 @@ export abstract class BrowserTypeBase implements BrowserType {
269269
helper.killProcess(launchedProcess);
270270
throw e;
271271
}
272-
browserServer = new BrowserServer(launchedProcess, gracefullyClose);
272+
browserServer = new BrowserServer(launchedProcess, gracefullyClose, kill);
273273
return { browserServer, downloadsPath, transport };
274274
}
275275

src/server/electron.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ export class Electron {
173173

174174
const logger = new RootLogger(options.logger);
175175
const electronArguments = ['--inspect=0', '--remote-debugging-port=0', '--require', path.join(__dirname, 'electronLoader.js'), ...args];
176-
const { launchedProcess, gracefullyClose } = await launchProcess({
176+
const { launchedProcess, gracefullyClose, kill } = await launchProcess({
177177
executablePath,
178178
args: electronArguments,
179179
env,
@@ -185,7 +185,7 @@ export class Electron {
185185
cwd: options.cwd,
186186
tempDirectories: [],
187187
attemptToGracefullyClose: () => app!.close(),
188-
onkill: (exitCode, signal) => {
188+
onExit: (exitCode, signal) => {
189189
if (app)
190190
app.emit(ElectronEvents.ElectronApplication.Close, exitCode, signal);
191191
},
@@ -198,7 +198,7 @@ export class Electron {
198198

199199
const chromeMatch = await waitForLine(launchedProcess, launchedProcess.stderr, /^DevTools listening on (ws:\/\/.*)$/, helper.timeUntilDeadline(deadline), timeoutError);
200200
const chromeTransport = await WebSocketTransport.connect(chromeMatch[1], logger, deadline);
201-
const browserServer = new BrowserServer(launchedProcess, gracefullyClose);
201+
const browserServer = new BrowserServer(launchedProcess, gracefullyClose, kill);
202202
const browser = await CRBrowser.connect(chromeTransport, { headful: true, logger, persistent: { viewport: null }, ownedServer: browserServer });
203203
app = new ElectronApplication(logger, browser, nodeConnection);
204204
await app._init();

src/server/processLauncher.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,14 @@ export type LaunchProcessOptions = {
5656

5757
// Note: attemptToGracefullyClose should reject if it does not close the browser.
5858
attemptToGracefullyClose: () => Promise<any>,
59-
onkill: (exitCode: number | null, signal: string | null) => void,
59+
onExit: (exitCode: number | null, signal: string | null) => void,
6060
logger: RootLogger,
6161
};
6262

6363
type LaunchResult = {
6464
launchedProcess: childProcess.ChildProcess,
6565
gracefullyClose: () => Promise<void>,
66+
kill: () => Promise<void>,
6667
};
6768

6869
export async function launchProcess(options: LaunchProcessOptions): Promise<LaunchResult> {
@@ -110,14 +111,14 @@ export async function launchProcess(options: LaunchProcessOptions): Promise<Laun
110111

111112
let processClosed = false;
112113
let fulfillClose = () => {};
113-
const waitForClose = new Promise(f => fulfillClose = f);
114+
const waitForClose = new Promise<void>(f => fulfillClose = f);
114115
let fulfillCleanup = () => {};
115-
const waitForCleanup = new Promise(f => fulfillCleanup = f);
116+
const waitForCleanup = new Promise<void>(f => fulfillCleanup = f);
116117
spawnedProcess.once('exit', (exitCode, signal) => {
117118
logger._log(browserLog, `<process did exit ${exitCode}, ${signal}>`);
118119
processClosed = true;
119120
helper.removeEventListeners(listeners);
120-
options.onkill(exitCode, signal);
121+
options.onExit(exitCode, signal);
121122
fulfillClose();
122123
// Cleanup as process exits.
123124
cleanup().then(fulfillCleanup);
@@ -175,7 +176,12 @@ export async function launchProcess(options: LaunchProcessOptions): Promise<Laun
175176
} catch (e) { }
176177
}
177178

178-
return { launchedProcess: spawnedProcess, gracefullyClose };
179+
function killAndWait() {
180+
killProcess();
181+
return waitForCleanup;
182+
}
183+
184+
return { launchedProcess: spawnedProcess, gracefullyClose, kill: killAndWait };
179185
}
180186

181187
export function waitForLine(process: childProcess.ChildProcess, inputStream: stream.Readable, regex: RegExp, timeout: number, timeoutError: TimeoutError): Promise<RegExpMatchArray> {

test/launcher.spec.js

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -223,8 +223,8 @@ describe('Browser.close', function() {
223223
});
224224
});
225225

226-
describe('browserType.launch |webSocket| option', function() {
227-
it('should support the webSocket option', async({browserType, defaultBrowserOptions}) => {
226+
describe('browserType.launchServer', function() {
227+
it('should work', async({browserType, defaultBrowserOptions}) => {
228228
const browserServer = await browserType.launchServer(defaultBrowserOptions);
229229
const browser = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() });
230230
const browserContext = await browser.newContext();
@@ -237,7 +237,7 @@ describe('browserType.launch |webSocket| option', function() {
237237
await browserServer._checkLeaks();
238238
await browserServer.close();
239239
});
240-
it('should fire "disconnected" when closing with webSocket', async({browserType, defaultBrowserOptions}) => {
240+
it('should fire "disconnected" when closing the server', async({browserType, defaultBrowserOptions}) => {
241241
const browserServer = await browserType.launchServer(defaultBrowserOptions);
242242
const browser = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() });
243243
const disconnectedEventPromise = new Promise(resolve => browser.once('disconnected', resolve));
@@ -248,6 +248,19 @@ describe('browserType.launch |webSocket| option', function() {
248248
closedPromise,
249249
]);
250250
});
251+
it('should fire "close" event during kill', async({browserType, defaultBrowserOptions}) => {
252+
const order = [];
253+
const browserServer = await browserType.launchServer(defaultBrowserOptions);
254+
const closedPromise = new Promise(f => browserServer.on('close', () => {
255+
order.push('closed');
256+
f();
257+
}));
258+
await Promise.all([
259+
browserServer.kill().then(() => order.push('killed')),
260+
closedPromise,
261+
]);
262+
expect(order).toEqual(['closed', 'killed']);
263+
});
251264
});
252265

253266
describe('browserType.connect', function() {

0 commit comments

Comments
 (0)