testem 3.19.0 → 3.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -3,17 +3,19 @@ Got Scripts? Test’em!
3
3
 
4
4
  [![Build Status](https://github.com/testem/testem/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/testem/testem/actions/workflows/ci.yml?query=branch%3Amaster) [![npm version](https://badge.fury.io/js/testem.svg)](http://badge.fury.io/js/testem)
5
5
 
6
+ Testem is a **JavaScript test runner** that runs your tests in **real desktop browsers**—Chrome, Firefox, Safari, Edge, and others you launch—so your specs execute in the same browser engines and DOM your users get, not a pretend environment. It also runs tests in **[Node](http://nodejs.org/)**, [PhantomJS](http://phantomjs.org/), or any launcher you configure. It is **framework-agnostic** and aimed at **any kind of tests** you want to run: unit, integration, end-to-end style suites, or custom setups—you pick the style; Testem wires it to the browser or process.
7
+
6
8
  Unit testing in Javascript can be tedious and painful, but Testem makes it so easy that you will actually *want* to write tests.
7
9
 
8
10
  Features
9
11
  --------
10
12
 
11
- * Test-framework agnostic. Support for
13
+ * Test-framework agnostic — designed for **any kind of tests** that fit your project (unit, integration, custom runners, etc.), not a single prescribed style. Support for
12
14
  - [Jasmine](http://jasmine.github.io/)
13
15
  - [QUnit](http://qunitjs.com/)
14
16
  - [Mocha](http://mochajs.org/)
15
17
  - Others, through custom test framework adapters.
16
- * Run tests in all major browsers as well as [Node](http://nodejs.org) and [PhantomJS](http://phantomjs.org/)
18
+ * Run tests in **all** major **real** browsers (your tests load and run in the actual browser) as well as [Node](http://nodejs.org) and [PhantomJS](http://phantomjs.org/)
17
19
  * Two distinct use-cases:
18
20
  - Test-Driven-Development(TDD) — designed to streamline the TDD workflow
19
21
  - Continuous Integration(CI) — designed to work well with popular CI servers like Jenkins or Teamcity
@@ -27,6 +29,8 @@ Features
27
29
  - JSHint/JSLint
28
30
  - everything else
29
31
 
32
+ **Internet Explorer** and **PhantomJS** are still supported as launchers, but we document them as **deprecated** targets: keeping them viable through transpilation and polyfills is likely to get more difficult over time, so prefer evergreen browsers or Node for new projects. See the [configuration reference](docs/config_file.md) for how we categorize browsers.
33
+
30
34
  Screencasts
31
35
  -----------
32
36
 
@@ -61,7 +65,7 @@ You will see a terminal-based interface which looks like this
61
65
 
62
66
  ![Initial interface](https://github.com/testem/testem/raw/master/images/initial.png)
63
67
 
64
- Now open your browser and go to the specified URL. You should now see
68
+ Now open a **real browser** (the URL Testem prints is a normal page in Chrome, Firefox, Safari, etc.) and go to the specified URL. You should now see
65
69
 
66
70
  ![Zero of zero](https://github.com/testem/testem/raw/master/images/zeros.png)
67
71
 
@@ -107,6 +111,28 @@ In development mode, Testem has a text-based graphical user interface which uses
107
111
  * d : half a page down target text panel
108
112
  * u : half a page up target text panel
109
113
 
114
+ ### File watching
115
+
116
+ In development mode, Testem watches your project directory for changes and re-runs tests when a
117
+ relevant file is added, edited, or removed. Watching is implemented with
118
+ [chokidar](https://github.com/paulmillr/chokidar) (v5).
119
+
120
+ * **`src_files`** — Glob patterns for source files whose changes should trigger a run (defaults to
121
+ `*.js` when unset). This is the main *watch list*.
122
+ * **`watch_files`** — Optional; if set, these patterns are watched instead of defaulting to
123
+ `src_files` (see `docs/config_file.md`).
124
+ * **`src_files_ignore`** — Patterns to exclude from the watch policy (e.g. `node_modules`).
125
+ * **`disable_watching`** — Set to `true` to turn off the file watcher entirely.
126
+
127
+ Testem watches the **current working directory** and applies your include/ignore patterns to
128
+ events from the watcher. You do not need to list every file explicitly; globs and ignores follow
129
+ the same policy as in the config reference.
130
+
131
+ **Troubleshooting:** On some setups (Docker, network filesystems, VMs), native `fs.watch` can be
132
+ flaky. Chokidar supports environment variables such as `CHOKIDAR_USE_POLLING=1` (force polling)
133
+ and `CHOKIDAR_INTERVAL` (polling interval in ms). See the
134
+ [chokidar readme](https://github.com/paulmillr/chokidar) for details.
135
+
110
136
  ### Command line options
111
137
 
112
138
  To see all command line options
@@ -134,9 +160,7 @@ Will print them out. The output might look like
134
160
 
135
161
  $ testem launchers
136
162
  Browsers available on this system:
137
- IE7
138
- IE8
139
- IE9
163
+ IE11
140
164
  Chrome
141
165
  Firefox
142
166
  Safari
@@ -144,8 +168,6 @@ Will print them out. The output might look like
144
168
  Opera
145
169
  PhantomJS
146
170
 
147
- Did you notice that this system has IE versions 7-9? Yes, actually it has only IE9 installed, but Testem uses IE's compatibility mode feature to emulate IE 7 and 8.
148
-
149
171
  When you run `testem ci` to run tests, it outputs the results in the [TAP](http://testanything.org/) format by default, which looks like
150
172
 
151
173
  ok 1 Chrome 16.0 - hello should say hello.
@@ -455,7 +477,8 @@ If you need to run a preprocessor (or indeed any shell command before the start
455
477
 
456
478
  "before_tests": "coffee -c *.coffee"
457
479
 
458
- And Testem will run it before each test run. For file watching, you may still use the `src_files` option
480
+ And Testem will run it before each test run. Point **`src_files`** at the sources you want
481
+ watched (see **File watching** under Development Mode above).
459
482
 
460
483
  ```javascript
461
484
  "src_files": [
package/lib/api.js CHANGED
@@ -1,6 +1,6 @@
1
1
 
2
2
 
3
- const log = require('npmlog');
3
+ const log = require('./log');
4
4
  const Config = require('./config');
5
5
  const EventEmitter = require('events').EventEmitter;
6
6
  const fs = require('fs');
package/lib/app.js CHANGED
@@ -1,9 +1,7 @@
1
-
2
-
3
1
  const EventEmitter = require('events').EventEmitter;
4
- const Bluebird = require('bluebird');
2
+ const { fromCallback, using, disposer, mapLimit } = require('./utils/promises');
5
3
  const Path = require('path');
6
- const log = require('npmlog');
4
+ const log = require('./log');
7
5
  const StyledString = require('styled_string');
8
6
  const _ = require('lodash');
9
7
 
@@ -40,7 +38,7 @@ module.exports = class App extends EventEmitter {
40
38
 
41
39
  let alreadyExit = false;
42
40
 
43
- this.cleanExit = err => {
41
+ this.cleanExit = (err) => {
44
42
  if (!alreadyExit) {
45
43
  alreadyExit = true;
46
44
 
@@ -52,7 +50,9 @@ module.exports = class App extends EventEmitter {
52
50
 
53
51
  if (this.testSuiteTimedOut === true) {
54
52
  let timeoutSeconds = this.testSuiteTimeout.timeout;
55
- err = new Error(`Test suite execution has timed out (config.timeout = ${timeoutSeconds} seconds). Terminated all test runners.`);
53
+ err = new Error(
54
+ `Test suite execution has timed out (config.timeout = ${timeoutSeconds} seconds). Terminated all test runners.`,
55
+ );
56
56
  exitCode = 1;
57
57
  }
58
58
 
@@ -64,53 +64,63 @@ module.exports = class App extends EventEmitter {
64
64
  start(cb) {
65
65
  log.info('Starting ' + this.config.appMode);
66
66
 
67
- return Bluebird.using(SignalListeners.with(), signalListeners => {
68
- signalListeners.on('signal', err => this.exit(err));
67
+ return using(SignalListeners.with(), (signalListeners) => {
68
+ signalListeners.on('signal', (err) => this.exit(err));
69
69
 
70
- return Bluebird.using(Reporter.with(this, this.stdoutStream, this.reportFileName), reporter => {
71
- this.reporter = reporter;
70
+ return using(
71
+ Reporter.with(this, this.stdoutStream, this.reportFileName),
72
+ (reporter) => {
73
+ this.reporter = reporter;
72
74
 
73
- return Bluebird.using(this.fileWatch(), () => {
74
- return Bluebird.using(this.getServer(), () => {
75
- return Bluebird.using(this.getRunners(), () => {
76
- return Bluebird.using(this.runHook('on_start'), () => {
77
- let w = this.waitForTests();
75
+ return using(this.fileWatch(), () => {
76
+ return using(this.getServer(), () => {
77
+ return using(this.getRunners(), () => {
78
+ return using(this.runHook('on_start'), () => {
79
+ let w = this.waitForTests();
78
80
 
79
- if (cb) {
80
- cb();
81
- }
81
+ if (cb) {
82
+ cb();
83
+ }
82
84
 
83
- return w;
84
- }).then(() => {
85
- log.info('Stopping ' + this.config.appMode);
85
+ return w;
86
+ })
87
+ .then(() => {
88
+ log.info('Stopping ' + this.config.appMode);
86
89
 
87
- this.emit('tests-finish');
90
+ this.emit('tests-finish');
88
91
 
89
- return Bluebird.using(this.runHook('on_exit'), () => {});
90
- }).catch(error => {
91
- log.error(error);
92
- log.info('Stopping ' + this.config.appMode);
92
+ return using(this.runHook('on_exit'), () => {});
93
+ })
94
+ .catch((error) => {
95
+ log.error(error);
96
+ log.info('Stopping ' + this.config.appMode);
93
97
 
94
- this.emit('tests-error');
98
+ this.emit('tests-error');
95
99
 
96
- return new Bluebird((resolve, reject) => {
97
- Bluebird.using(this.runHook('on_exit'), () => {}).then(() => {
98
- reject(error);
100
+ return new Promise((resolve, reject) => {
101
+ using(this.runHook('on_exit'), () => {}).then(() => {
102
+ reject(error);
103
+ });
104
+ });
99
105
  });
100
- });
101
106
  });
102
107
  });
103
108
  });
104
- });
105
- });
106
- }).asCallback(this.cleanExit);
109
+ },
110
+ );
111
+ }).then(
112
+ () => this.cleanExit(),
113
+ (err) => this.cleanExit(err),
114
+ );
107
115
  }
108
116
 
109
117
  waitForTests() {
110
118
  log.info('Waiting for tests.');
111
119
 
112
120
  if (this.exited) {
113
- return Bluebird.reject(this.exitErr || new Error('Testem exited before running any tests.'));
121
+ return Promise.reject(
122
+ this.exitErr || new Error('Testem exited before running any tests.'),
123
+ );
114
124
  }
115
125
 
116
126
  let run = this.triggerRun('Start');
@@ -119,7 +129,7 @@ module.exports = class App extends EventEmitter {
119
129
  run.then(() => this.exit());
120
130
  }
121
131
 
122
- return new Bluebird.Promise((resolve, reject) => {
132
+ return new Promise((resolve, reject) => {
123
133
  this.on('testFinish', resolve);
124
134
  this.on('testError', reject);
125
135
  });
@@ -133,38 +143,42 @@ module.exports = class App extends EventEmitter {
133
143
  }
134
144
  this.restarting = true;
135
145
 
136
- return this.stopCurrentRun().catch(this.exit.bind(this)).then(() => {
137
- this.restarting = false;
146
+ return this.stopCurrentRun()
147
+ .catch(this.exit.bind(this))
148
+ .then(() => {
149
+ this.restarting = false;
138
150
 
139
- return this.runTests();
140
- });
151
+ return this.runTests();
152
+ });
141
153
  }
142
154
 
143
155
  stopCurrentRun() {
144
156
  if (!this.currentRun) {
145
- return Bluebird.resolve();
157
+ return Promise.resolve();
146
158
  }
147
159
 
148
- return Bluebird.all([ this.stopRunners(), this.currentRun ]);
160
+ return Promise.all([this.stopRunners(), this.currentRun]);
149
161
  }
150
162
 
151
163
  runTests() {
152
164
  if (this.paused) {
153
- return Bluebird.resolve();
165
+ return Promise.resolve();
154
166
  }
155
167
 
156
168
  log.info('Running tests...');
157
169
 
158
170
  this.reporter.onStart('testem', { launcherId: 0 });
159
171
 
160
- return Bluebird.using(this.runHook('before_tests'), () => {
161
- return Bluebird.using(RunTimeout.with(this.config.get('timeout')), timeout => {
172
+ return using(this.runHook('before_tests'), () => {
173
+ return using(RunTimeout.with(this.config.get('timeout')), (timeout) => {
162
174
  this.testSuiteTimeout = timeout;
163
175
 
164
176
  timeout.on('timeout', () => {
165
177
  let timeoutSeconds = timeout.timeout;
166
178
 
167
- log.info(`Test suite execution has timed out (config.timeout = ${timeoutSeconds} seconds). Terminating all test runners`);
179
+ log.info(
180
+ `Test suite execution has timed out (config.timeout = ${timeoutSeconds} seconds). Terminating all test runners`,
181
+ );
168
182
  this.testSuiteTimedOut = true;
169
183
  this.killRunners();
170
184
  });
@@ -176,25 +190,27 @@ module.exports = class App extends EventEmitter {
176
190
 
177
191
  return this.currentRun;
178
192
  }).then(() => {
179
- return Bluebird.using(this.runHook('after_tests'), () => {});
193
+ return using(this.runHook('after_tests'), () => {});
180
194
  });
181
- }).catch(err => {
182
- if (err.hideFromReporter) {
183
- return;
184
- }
185
-
186
- let result = {
187
- failed: 1,
188
- passed: 0,
189
- name: 'testem',
190
- launcherId: 0,
191
- error: {
192
- message: err.toString()
195
+ })
196
+ .catch((err) => {
197
+ if (err.hideFromReporter) {
198
+ return;
193
199
  }
194
- };
195
200
 
196
- this.reporter.report('testem', result);
197
- }).finally(() => this.reporter.onEnd('testem', { launcherId: 0 }));
201
+ let result = {
202
+ failed: 1,
203
+ passed: 0,
204
+ name: 'testem',
205
+ launcherId: 0,
206
+ error: {
207
+ message: err.toString(),
208
+ },
209
+ };
210
+
211
+ this.reporter.report('testem', result);
212
+ })
213
+ .finally(() => this.reporter.onEnd('testem', { launcherId: 0 }));
198
214
  }
199
215
 
200
216
  exit(err, cb) {
@@ -221,7 +237,7 @@ module.exports = class App extends EventEmitter {
221
237
  return;
222
238
  }
223
239
 
224
- startServer(callback) {
240
+ startServer() {
225
241
  log.info('Starting server');
226
242
  this.server = new Server(this.config);
227
243
  this.server.on('file-requested', this.onFileRequested.bind(this));
@@ -229,16 +245,29 @@ module.exports = class App extends EventEmitter {
229
245
  this.server.on('browser-relogin', this.onBrowserRelogin.bind(this));
230
246
  this.server.on('server-error', this.onServerError.bind(this));
231
247
 
232
- return this.server.start().asCallback(callback);
248
+ return this.server.start();
233
249
  }
234
250
 
235
251
  getServer() {
236
- return this.startServer().disposer(() => this.stopServer());
252
+ return disposer(Promise.resolve(this.startServer()), () =>
253
+ this.stopServer(),
254
+ );
237
255
  }
238
256
 
239
- onFileRequested(filepath) {
257
+ /**
258
+ * When the server requests a file, add it to the file watcher if it is not already being watched.
259
+ * If the file watcher is disabled, do nothing.
260
+ *
261
+ * @param {string} filepath
262
+ * @returns {Promise<void>}
263
+ */
264
+ async onFileRequested(filepath) {
240
265
  if (this.fileWatcher && !this.config.get('serve_files')) {
241
- this.fileWatcher.add(filepath);
266
+ try {
267
+ await this.fileWatcher.add(filepath);
268
+ } catch (err) {
269
+ log.error(err);
270
+ }
242
271
  }
243
272
  }
244
273
 
@@ -251,18 +280,30 @@ module.exports = class App extends EventEmitter {
251
280
  }
252
281
 
253
282
  onBrowserLogin(browserName, id, socket) {
254
- let browser = _.find(this.runners, runner => {
255
- return runner.launcherId === id && (!runner.socket || !runner.socket.connected);
283
+ let browser = _.find(this.runners, (runner) => {
284
+ return (
285
+ runner.launcherId === id && (!runner.socket || !runner.socket.connected)
286
+ );
256
287
  });
257
288
 
258
289
  if (!browser) {
259
- let launcher = new LauncherFactory(browserName, {
260
- id: id,
261
- protocol: 'browser'
262
- }, this.config).create();
290
+ let launcher = new LauncherFactory(
291
+ browserName,
292
+ {
293
+ id: id,
294
+ protocol: 'browser',
295
+ },
296
+ this.config,
297
+ ).create();
263
298
  const singleRun = this.config.get('single_run');
264
299
 
265
- browser = new BrowserTestRunner(launcher, this.reporter, this.runnerIndex++, singleRun, this.config);
300
+ browser = new BrowserTestRunner(
301
+ launcher,
302
+ this.reporter,
303
+ this.runnerIndex++,
304
+ singleRun,
305
+ this.config,
306
+ );
266
307
  this.addRunner(browser);
267
308
  }
268
309
 
@@ -270,10 +311,12 @@ module.exports = class App extends EventEmitter {
270
311
  }
271
312
 
272
313
  onBrowserRelogin(browserName, id, socket) {
273
- let browser = _.find(this.runners, runner => {
314
+ let browser = _.find(this.runners, (runner) => {
274
315
  // a browser relogin can happen if a client socket was disconnected, which may not be reflected in runner.socket's connected state
275
316
  // or if the socket was nulled by 'onDisconnect'
276
- return runner.launcherId === id && (runner.socket || runner.socket === null);
317
+ return (
318
+ runner.launcherId === id && (runner.socket || runner.socket === null)
319
+ );
277
320
  });
278
321
 
279
322
  if (!browser) {
@@ -293,55 +336,74 @@ module.exports = class App extends EventEmitter {
293
336
  this.emit('runnerAdded', runner);
294
337
  }
295
338
 
339
+ // Watcher teardown is this disposer's cleanup (using() awaits it on success and on setup failure).
296
340
  fileWatch() {
297
- return this.configureFileWatch().disposer(() => {});
341
+ return disposer(Promise.resolve(this.configureFileWatch()), () => {
342
+ if (this.fileWatcher) {
343
+ const w = this.fileWatcher;
344
+ this.fileWatcher = undefined;
345
+ return w.close();
346
+ }
347
+ });
298
348
  }
299
349
 
300
- configureFileWatch(cb) {
350
+ configureFileWatch() {
301
351
  if (this.config.get('disable_watching')) {
302
- return Bluebird.resolve().asCallback(cb);
352
+ return Promise.resolve();
303
353
  }
304
354
 
305
- this.fileWatcher = new FileWatcher(this.config);
306
- this.fileWatcher.on('fileChanged', filepath => {
307
- log.info(filepath + ' changed (' + (this.disableFileWatch ? 'disabled' : 'enabled') + ').');
308
- if (this.disableFileWatch || this.paused) {
309
- return;
310
- }
311
- let configFile = this.config.get('file');
312
- if ((configFile && filepath === Path.resolve(configFile)) ||
313
- (this.config.isCwdMode() && filepath === process.cwd())) {
314
- // config changed
315
- this.configure(() => {
316
- this.triggerRun('Config changed');
317
- });
318
- } else {
319
- Bluebird.using(this.runHook('on_change', {file: filepath}), () => {
320
- this.triggerRun('File changed: ' + filepath);
321
- });
322
- }
323
- });
324
- this.fileWatcher.on('EMFILE', () => {
325
- let view = this.view;
326
- let text = [
327
- 'The file watcher received a EMFILE system error, which means that ',
328
- 'it has hit the maximum number of files that can be open at a time. ',
329
- 'Luckily, you can increase this limit as a workaround. See the directions below \n \n',
330
- 'Linux: http://stackoverflow.com/a/34645/5304\n',
331
- 'Mac OS: http://serverfault.com/a/15575/47234'
332
- ].join('');
333
- view.setErrorPopupMessage(new StyledString(text + '\n ').foreground('megenta'));
355
+ return FileWatcher.create(this.config).then((fw) => {
356
+ this.fileWatcher = fw;
357
+ this.fileWatcher.on('fileChanged', (filepath) => {
358
+ log.info(
359
+ filepath +
360
+ ' changed (' +
361
+ (this.disableFileWatch ? 'disabled' : 'enabled') +
362
+ ').',
363
+ );
364
+ if (this.disableFileWatch || this.paused) {
365
+ return;
366
+ }
367
+ let configFile = this.config.get('file');
368
+ if (
369
+ (configFile && filepath === Path.resolve(configFile)) ||
370
+ (this.config.isCwdMode() && filepath === process.cwd())
371
+ ) {
372
+ // config changed
373
+ this.configure(() => {
374
+ this.triggerRun('Config changed');
375
+ });
376
+ } else {
377
+ using(this.runHook('on_change', { file: filepath }), () => {
378
+ this.triggerRun('File changed: ' + filepath);
379
+ });
380
+ }
381
+ });
382
+ this.fileWatcher.on('EMFILE', () => {
383
+ let view = this.view;
384
+ let text = [
385
+ 'The file watcher received a EMFILE system error, which means that ',
386
+ 'it has hit the maximum number of files that can be open at a time. ',
387
+ 'Luckily, you can increase this limit as a workaround. See the directions below \n \n',
388
+ 'Linux: http://stackoverflow.com/a/34645/5304\n',
389
+ 'Mac OS: http://serverfault.com/a/15575/47234',
390
+ ].join('');
391
+ view.setErrorPopupMessage(
392
+ new StyledString(text + '\n ').foreground('megenta'),
393
+ );
394
+ });
334
395
  });
335
-
336
- return Bluebird.resolve().asCallback(cb);
337
396
  }
338
397
 
339
398
  getRunners() {
340
- return Bluebird.fromCallback(callback => {
341
- this.createRunners(callback);
342
- }).disposer(() => {
343
- return this.killRunners();
344
- });
399
+ return disposer(
400
+ fromCallback((callback) => {
401
+ this.createRunners(callback);
402
+ }),
403
+ () => {
404
+ return this.killRunners();
405
+ },
406
+ );
345
407
  }
346
408
 
347
409
  createRunners(callback) {
@@ -374,56 +436,55 @@ module.exports = class App extends EventEmitter {
374
436
  case 'tap':
375
437
  return TapProcessTestRunner;
376
438
  default:
377
- throw new Error('Don\'t know about ' + protocol + ' protocol.');
439
+ throw new Error('Unknown protocol: ' + protocol);
378
440
  }
379
441
  }
380
442
 
381
443
  createTestRunner(launcher, reporter) {
382
444
  let singleRun = this.config.get('single_run');
383
445
 
384
- return new (this.getRunnerFactory(launcher))(launcher, reporter, this.runnerIndex++, singleRun, this.config);
446
+ return new (this.getRunnerFactory(launcher))(
447
+ launcher,
448
+ reporter,
449
+ this.runnerIndex++,
450
+ singleRun,
451
+ this.config,
452
+ );
385
453
  }
386
454
 
387
455
  withTestTimeout() {
388
- return this.startClock().disposer(() => {
456
+ return disposer(this.startClock(), () => {
389
457
  return this.cancelExistingTimeout();
390
458
  });
391
459
  }
392
460
 
393
461
  singleRun(timeout) {
394
462
  let limit = this.config.get('parallel');
463
+ let concurrency = limit && limit >= 1 ? parseInt(limit) : Infinity;
395
464
 
396
- let options = {};
397
-
398
- if (limit && limit >= 1) {
399
- options.concurrency = parseInt(limit);
400
- } else {
401
- options.concurrency = Infinity;
402
- }
403
-
404
- return Bluebird.map(this.runners, (runner) => {
465
+ return mapLimit(this.runners, concurrency, (runner) => {
405
466
  if (this.exited) {
406
467
  let e = new Error('Run canceled.');
407
468
  e.hideFromReporter = true;
408
- return Bluebird.reject(e);
469
+ return Promise.reject(e);
409
470
  }
410
471
  if (this.restarting) {
411
- return Bluebird.resolve();
472
+ return Promise.resolve();
412
473
  }
413
474
  return timeout.try(() => runner.start());
414
- }, options);
475
+ });
415
476
  }
416
477
 
417
478
  wrapUp(err) {
418
479
  this.exit(err);
419
480
  }
420
481
 
421
- stopServer(callback) {
482
+ stopServer() {
422
483
  if (!this.server) {
423
- return Bluebird.resolve().asCallback(callback);
484
+ return Promise.resolve();
424
485
  }
425
486
 
426
- return this.server.stop().asCallback(callback);
487
+ return this.server.stop();
427
488
  }
428
489
 
429
490
  getExitCode() {
@@ -442,20 +503,22 @@ module.exports = class App extends EventEmitter {
442
503
  }
443
504
 
444
505
  stopRunners() {
445
- return Bluebird.each(this.runners, runner => {
446
- if (typeof runner.stop === 'function') {
447
- return runner.stop();
448
- }
506
+ return Promise.all(
507
+ this.runners.map((runner) => {
508
+ if (typeof runner.stop === 'function') {
509
+ return runner.stop();
510
+ }
449
511
 
450
- return runner.exit();
451
- });
512
+ return runner.exit();
513
+ }),
514
+ );
452
515
  }
453
516
 
454
517
  killRunners() {
455
- return Bluebird.each(this.runners, runner => runner.exit());
518
+ return Promise.all(this.runners.map((runner) => runner.exit()));
456
519
  }
457
520
 
458
521
  launchers() {
459
- return this.runners.map(runner => runner.launcher);
522
+ return this.runners.map((runner) => runner.launcher);
460
523
  }
461
524
  };