Nightwatch not terminating after browser.end()
<p>I'm running Nightwatch after launching a child process that starts up my local servers. Nightwatch runs the tests, they complete successfully, and the browser windows all close, but the <code>nightwatch</code> process continues to run after printing the message "OK. 10  total assertions passed.".</p>

<p>I thought it may have something to do with how I'm watching events on the <code>nightwatch</code> process, but as far as I can tell I am watching all events that would indicate Nightwatch is exiting.</p>

<p>The method <code>shutdown()</code> in <code>runner.js</code> is never called. How do I get Nightwatch to terminate when the tests finish?</p>

<p><strong>Update</strong></p>

<p>If I remove the last test in <code>sign-in.js</code> then Nightwatch exits as expected.</p>

<p><strong>runner.js</strong></p>

<pre><code>import spawn from 'cross-spawn'

// 1. start the dev server using production config
process.env.NODE_ENV = 'testing'

let servers

function shutdown (result) {
  console.log('HERE', result)
  try {
    // Passing a negative PID to kill will terminate all child processes, not just the parent
    if (servers) process.kill(-servers.pid)
  } catch (e) {
    console.error('Unable to shutdown servers, may need to be killed manually')
  }

  if (result) {
    console.error(result)
    process.exit(1)
  } else {
    process.exit(0)
  }
}

function watch (child) {
  child.on('close', shutdown)
  child.on('disconnect', shutdown)
  child.on('error', shutdown)
  child.on('exit', shutdown)
  child.on('uncaughtException', shutdown)
}

try {
  servers = spawn('yarn', ['run', 'dev-all'], { cwd: '..', stdio: 'inherit', detached: true })
  watch(servers)

  // 2. run the nightwatch test suite against it
  // to run in additional browsers:
  //    1. add an entry in test/e2e/nightwatch.conf.json under "test_settings"
  //    2. add it to the --env flag below
  // or override the environment flag, for example: `npm run e2e -- --env chrome,firefox`
  // For more information on Nightwatch's config file, see
  // http://nightwatchjs.org/guide#settings-file
  var opts = process.argv.slice(2)
  if (opts.indexOf('--config') === -1) {
    opts = opts.concat(['--config', 'e2e/nightwatch.conf.js'])
  }
  if (opts.indexOf('--env') === -1) {
    opts = opts.concat(['--env', 'chrome'])
  }

  var runner = spawn('./node_modules/.bin/nightwatch', opts, { stdio: 'inherit' })
  watch(runner)
  watch(process)
} catch (error) {
  shutdown(error)
}
</code></pre>

<p><strong>nightwatch.conf.js</strong></p>

<pre><code>require('babel-register')
var config = require('../../frontend/config')

// http://nightwatchjs.org/guide#settings-file
module.exports = {
  src_folders: ['e2e/specs'],
  output_folder: 'e2e/reports',
  custom_assertions_path: ['e2e/custom-assertions'],

  selenium: {
    start_process: true,
    server_path: 'node_modules/selenium-server/lib/runner/selenium-server-standalone-3.0.1.jar',
    host: '127.0.0.1',
    port: 4444,
    cli_args: {
      'webdriver.chrome.driver': require('chromedriver').path
    }
  },

  test_settings: {
    default: {
      selenium_port: 4444,
      selenium_host: 'localhost',
      silent: true,
      globals: {
        devServerURL: 'http://localhost:' + (process.env.PORT || config.dev.port)
      }
    },

    chrome: {
      desiredCapabilities: {
        browserName: 'chrome',
        javascriptEnabled: true,
        acceptSslCerts: true
      }
    },

    firefox: {
      desiredCapabilities: {
        browserName: 'firefox',
        javascriptEnabled: true,
        acceptSslCerts: true
      }
    }
  }
}
</code></pre>

<p><strong>sign-in.js</strong> (one of the tests)</p>

<pre><code>import firebase from 'firebase-admin'
import uuid from 'uuid'

import * as firebaseSettings from '../../../backend/src/firebase-settings'

const PASSWORD = 'toomanysecrets'

function createUser (user) {
  console.log('Creating user', user.uid)
  let db = firebase.database()
  return Promise.all([
    firebase.auth().createUser({
      uid: user.uid,
      email: user.email,
      emailVerified: true,
      displayName: user.fullName,
      password: PASSWORD
    }),
    db.ref('users').child(user.uid).set({
      email: user.email,
      fullName: user.fullName
    }),
    db.ref('roles').child(user.uid).set({
      instructor: false
    })
  ])
}

function destroyUser (user) {
  if (!user) return

  console.log('Removing user', user.uid)
  let db = firebase.database()
  try { db.ref('roles').child(user.uid).remove() } catch (e) {}
  try { db.ref('users').child(user.uid).remove() } catch (e) {}
  try { firebase.auth().deleteUser(user.uid) } catch (e) {}
}

module.exports = {
  'Sign In links exist': browser =&gt; {
    // automatically uses dev Server port from /config.index.js
    // default: http://localhost:8080
    // see nightwatch.conf.js
    const devServer = browser.globals.devServerURL

    browser
      .url(devServer)
      .waitForElementVisible('#container', 5000)

    browser.expect.element('.main-nav').to.be.present
    browser.expect.element('.main-nav a[href^=\'https://oauth.ais.msu.edu/oauth/authorize\']').to.be.present
    browser.expect.element('.main-nav a[href^=\'/email-sign-in\']').to.be.present
    browser.end()
  },
  'Successful Sign In with Email shows dashboard': browser =&gt; {
    const devServer = browser.globals.devServerURL

    firebase.initializeApp(firebaseSettings.appConfig)

    let userId = uuid.v4()
    let user = {
      uid: userId,
      email: `${userId}@test.com`,
      fullName: 'Test User'
    }

    createUser(user)

    browser.url(devServer)
      .waitForElementVisible('.main-nav a[href^=\'/email-sign-in\']', 5000)
      .click('.main-nav a[href^=\'/email-sign-in\']')
      .waitForElementVisible('button', 5000)
      .setValue('input[type=text]', user.email)
      .setValue('input[type=password]', PASSWORD)
      .click('button')
      .waitForElementVisible('.main-nav a[href^=\'/sign-out\']', 5000)
      .end(() =&gt; {
        destroyUser(user)
      })
  }
}
</code></pre>

<p>After the tests complete successfully, I see the following:</p>

<pre><code>grimlock:backend egillespie$ ps -ef | grep nightwatch
501 13087 13085   0  1:51AM ttys000    0:02.18 node ./node_modules/.bin/nightwatch --presets es2015,stage-0 --config e2e/nightwatch.conf.js --env chrome
</code></pre>