Introduction of Nightmare.js

What is Nightmare.js

A high-level browser library
More about Nightmare.js
Benchmarks

Nightmare running via Phantom and Electron

Getting Started
Installation

Install  NodeJS 4.x or higher and NPM first


npm install nightmare --save

 

Run the example
// example1.test.js

const Nightmare = require('nightmare')
const nightmare = Nightmare({ show: true })

nightmare
  .goto('https://duckduckgo.com')
  .type('#search_form_input_homepage', 'github nightmare')
  .click('#search_button_homepage')
  .wait('#r1-0 a.result__a')
  .evaluate(() => document.querySelector('#r1-0 a.result__a').href)
  .end()
  .then(console.log)
  .catch(error => {
    console.error('Search failed:', error)
  })

Result

Popup a browser within new window

Result

Show the result in the console

Combination with Javascript Test Framework
Installation


npm install mocha --save

 

npm install mocha -g

 

 


npm install  Chai --save

 

Chai.js - BDD / TDD assertion library

Run test with mocha
// example2.test.js

const Nightmare = require('nightmare')
const nightmare = Nightmare({ show: true })
const chai = require('chai')
const expect = chai.expect

describe('test duckduckgo search results', () => {
  it('should find the nightmare github link first', function(done) {
    this.timeout('10s')

    nightmare
      .goto('https://duckduckgo.com')
      .type('#search_form_input_homepage', 'github nightmare')
      .click('#search_button_homepage')
      .wait('#links .result__a')
      .evaluate(() => document.querySelector('#links .result__a').href)
      .end()
      .then(link => {
        expect(link).to.equal('https://github.com/segmentio/nightmare')
        done()
      })
  })
})
Run test with mocha
// example2.test.js

const Nightmare = require('nightmare')
const nightmare = Nightmare({ show: true })
const chai = require('chai')
const expect = chai.expect
/* 

describe('test duckduckgo search results', () => {
  it('should find the nightmare github link first', function(done) {
    this.timeout('10s')

    // const nightmare = Nightmare()
    nightmare
      .goto('https://duckduckgo.com')
      .type('#search_form_input_homepage', 'github nightmare')
      .click('#search_button_homepage')
      .wait('#links .result__a')
      .evaluate(() => document.querySelector('#links .result__a').href)
      .end()
      .then(link => {
        expect(link).to.equal('https://github.com/segmentio/nightmare')
        done()
      })
  })
})
*/

Import library

Popup debug window

Run test with mocha
// example2.test.js

/* 
const Nightmare = require('nightmare')
const nightmare = Nightmare({ show: true })
const chai = require('chai')
const expect = chai.expect
*/

describe('test duckduckgo search results', () => {
  it('should find the nightmare github link first', function(done) {
    this.timeout('10s')

    nightmare
      .goto('https://duckduckgo.com')
      .type('#search_form_input_homepage', 'github nightmare')
      .click('#search_button_homepage')
      .wait('#links .result__a')
      .evaluate(() => document.querySelector('#links .result__a').href)
      .end()
      .then(link => {
        expect(link).to.equal('https://github.com/segmentio/nightmare')
        done()
      })
  })
})

Test Case

Result
Result

Total Execution time : 9s

Mocha API Timeout Setting
// example2.test.js

/*
const Nightmare = require('nightmare')
const nightmare = Nightmare({ show: true })
const chai = require('chai')
const expect = chai.expect
*/
/*
describe('test duckduckgo search results', () => {
  it('should find the nightmare github link first', function(done) {
*/
    this.timeout('10s')
/*
    nightmare
      .goto('https://duckduckgo.com')
      .type('#search_form_input_homepage', 'github nightmare')
      .click('#search_button_homepage')
      .wait('#links .result__a')
      .evaluate(() => document.querySelector('#links .result__a').href)
      .end()
      .then(link => {
        expect(link).to.equal('https://github.com/segmentio/nightmare')
        done()
      })
  })
})
*/

Mocha test case

 default timeout: 10s

Mocha API Timeout Setting
// example2.test.js

/*
const Nightmare = require('nightmare')
const nightmare = Nightmare({ show: true })
const chai = require('chai')
const expect = chai.expect
*/
/*
describe('test duckduckgo search results', () => {
  it('should find the nightmare github link first', function(done) {
*/
    this.timeout('10s')
    nightmare.wait(5000)
/*
    nightmare
      .goto('https://duckduckgo.com')
      .type('#search_form_input_homepage', 'github nightmare')
      .click('#search_button_homepage')
      .wait('#links .result__a')
      .evaluate(() => document.querySelector('#links .result__a').href)
      .end()
      .then(link => {
        expect(link).to.equal('https://github.com/segmentio/nightmare')
        done()
      })
  })
})
*/

We add more 5s execution time

Mocha API Timeout Setting
Mocha API Timeout Setting
// example2.test.js

/*
const Nightmare = require('nightmare')
const nightmare = Nightmare({ show: true })
const chai = require('chai')
const expect = chai.expect
*/
/*
describe('test duckduckgo search results', () => {
  it('should find the nightmare github link first', function(done) {
*/
    this.timeout('15s')
/*
    nightmare.wait(5000)
    nightmare
      .goto('https://duckduckgo.com')
      .type('#search_form_input_homepage', 'github nightmare')
      .click('#search_button_homepage')
      .wait('#links .result__a')
      .evaluate(() => document.querySelector('#links .result__a').href)
      .end()
      .then(link => {
        expect(link).to.equal('https://github.com/segmentio/nightmare')
        done()
      })
  })
})
*/

We modify timeout setting to 15s

Mocha API Timeout Setting

Nightmare API

Nightmare API List
API List

Set up an instance

Basic

Advanced

  • waitTimeout(default: 30s)
  • gotoTimeout (default: 30s)
  • loadTimeout  (default: infinite)
  • executionTimeout (default: 30s)
  • paths
  • switches
  • dock (OS X)
  • penDevTools
  • typeInterval (default: 100ms)
  • pollInterval (default: 250ms)
  • maxAuthRetries (default: 3)
API List

Interact with the page

Extract from the page

  • .click
  • .mousedown
  • .mouseup
  • .mouseover
  • .mouseout
  • .type
  • .insert
  • .scrollTo
  • .inject
  • .wait
  • .header

Http

  • .goto
  • .back
  • .forward
  • .refresh

Mouse

Checkbox

  • .check
  • .uncheck

Select

  • .select()

Others

  • .exists
  • .visible
  • .on
  • .once
  • .removeListener
  • .screenshot
  • .html
  • .pdf
  • .title
  • .url
  • .path
API List - Advanced

Cookie

  • .cookies.get (name|path)
  • .cookies.get
  • .cookies.set
  • .cookies.clear
  • .cookies.clearAll

Proxies

  •  Link to local/remote proxy server

Promises

  • Nightmare uses default native ES6 promises
  • Support Async/Await (Node.js ver7.10+)

Extending Nightmare

  • Add your own custom actions to the Nightmare prototype

API List - goto

// example1.test.js

// const Nightmare = require('nightmare')
// const nightmare = Nightmare({ show: true })

/*
  @ name goto(url, [headers])
  @ return {
     url: The URL that was loaded
     code: The HTTP status code (e.g. 200, 404, 500)
     method: The HTTP method used (e.g. "GET", "POST")
     referrer: The page that the window was displaying prior to this load or an empty string if this is the first page load.
     headers: An object representing the response headers for the request as in 
              {header1-name: header1-value, header2-name: header2-value}
    }
*/
nightmare
  .goto('https://duckduckgo.com') 
//  .type('#search_form_input_homepage', 'github nightmare')
//  .click('#search_button_homepage')
//  .wait('#r1-0 a.result__a')
//  .evaluate(() => document.querySelector('#r1-0 a.result__a').href)
//  .end()
//  .then(console.log)
//  .catch(error => {
//    console.error('Search failed:', error)
  })

 @ goto(url, [headers])

    -  Load the page at url

 @ return successful {

  • url:
  • code: The HTTP status code
  • method: HTTP method
  • referrer:
    • The page that the window was displaying prior to this load
    • empty string if this is the first page load.
  • headers:  response headers for the request as in {header1-name: header1-value, header2-name: header2-value}

   }

API List - goto

// example1.test.js

// const Nightmare = require('nightmare')
// const nightmare = Nightmare({ show: true })

/*
  @ name goto(url, [headers])
  @ return {
     url: The URL that was loaded
     code: The HTTP status code (e.g. 200, 404, 500)
     method: The HTTP method used (e.g. "GET", "POST")
     referrer: The page that the window was displaying prior to this load or an empty string if this is the first page load.
     headers: An object representing the response headers for the request as in 
              {header1-name: header1-value, header2-name: header2-value}
    }
*/
nightmare
  .goto('https://duckduckgo.com') 
//  .type('#search_form_input_homepage', 'github nightmare')
//  .click('#search_button_homepage')
//  .wait('#r1-0 a.result__a')
//  .evaluate(() => document.querySelector('#r1-0 a.result__a').href)
//  .end()
//  .then(console.log)
//  .catch(error => {
//    console.error('Search failed:', error)
  })

 @ goto(url, [headers])

    -  Load the page at url

 @ return failed {

  • message
  • code: This is NOT the HTTP status code,. It return chromium error code
  • details: details about the error. This may be null or an empty string.
  • url

   }

API List - type

// example1.test.js

// const Nightmare = require('nightmare')
// const nightmare = Nightmare({ show: true })

/*
  @ name goto(url, [headers])
  @ return {
     url: The URL that was loaded
     code: The HTTP status code (e.g. 200, 404, 500)
     method: The HTTP method used (e.g. "GET", "POST")
     referrer: The page that the window was displaying prior to this load or an empty string if this is the first page load.
     headers: An object representing the response headers for the request as in 
              {header1-name: header1-value, header2-name: header2-value}
    }
*/
nightmare
//  .goto('https://duckduckgo.com') 
  .type('#search_form_input_homepage', 'github nightmare')
//  .click('#search_button_homepage')
//  .wait('#r1-0 a.result__a')
//  .evaluate(() => document.querySelector('#r1-0 a.result__a').href)
//  .end()
//  .then(console.log)
//  .catch(error => {
//    console.error('Search failed:', error)
  })

 @ type(selector [, text])

    -  Simulate user typing text in selector element

    - Empty or falsey values provided for text

API List - click

// example1.test.js

// const Nightmare = require('nightmare')
// const nightmare = Nightmare({ show: true })

/*
  @ name goto(url, [headers])
  @ return {
     url: The URL that was loaded
     code: The HTTP status code (e.g. 200, 404, 500)
     method: The HTTP method used (e.g. "GET", "POST")
     referrer: The page that the window was displaying prior to this load or an empty string if this is the first page load.
     headers: An object representing the response headers for the request as in 
              {header1-name: header1-value, header2-name: header2-value}
    }
*/
nightmare
//  .goto('https://duckduckgo.com') 
//  .type('#search_form_input_homepage', 'github nightmare')
  .click('#search_button_homepage')
//  .wait('#r1-0 a.result__a')
//  .evaluate(() => document.querySelector('#r1-0 a.result__a').href)
//  .end()
//  .then(console.log)
//  .catch(error => {
//    console.error('Search failed:', error)
  })

 @ click(selector)

    -  Clicks the selector element once.

API List - wait

// example1.test.js

// const Nightmare = require('nightmare')
// const nightmare = Nightmare({ show: true })

/*
  @ name goto(url, [headers])
  @ return {
     url: The URL that was loaded
     code: The HTTP status code (e.g. 200, 404, 500)
     method: The HTTP method used (e.g. "GET", "POST")
     referrer: The page that the window was displaying prior to this load or an empty string if this is the first page load.
     headers: An object representing the response headers for the request as in 
              {header1-name: header1-value, header2-name: header2-value}
    }
*/
nightmare
//  .goto('https://duckduckgo.com') 
//  .type('#search_form_input_homepage', 'github nightmare')
//  .click('#search_button_homepage')
  .wait('#r1-0 a.result__a')
//  .evaluate(() => document.querySelector('#r1-0 a.result__a').href)
//  .end()
//  .then(console.log)
//  .catch(error => {
//    console.error('Search failed:', error)
  })

 @ wait (ms|selector|fn[, arg1, arg2,...])

    - ms: Waits for ms milliseconds e.g. wait(5000).

    - selector: Waits until the element selector is present

    -  Waits until the fn evaluated on the page with arg1, arg2,... returns true.

API List - end

// example1.test.js

// const Nightmare = require('nightmare')
// const nightmare = Nightmare({ show: true })

/*
  @ name goto(url, [headers])
  @ return {
     url: The URL that was loaded
     code: The HTTP status code (e.g. 200, 404, 500)
     method: The HTTP method used (e.g. "GET", "POST")
     referrer: The page that the window was displaying prior to this load or an empty string if this is the first page load.
     headers: An object representing the response headers for the request as in 
              {header1-name: header1-value, header2-name: header2-value}
    }
*/
nightmare
//  .goto('https://duckduckgo.com') 
//  .type('#search_form_input_homepage', 'github nightmare')
//  .click('#search_button_homepage')
//  .wait('#r1-0 a.result__a')
//  .evaluate(() => document.querySelector('#r1-0 a.result__a').href)
  .end()
//  .then(console.log)
//  .catch(error => {
//    console.error('Search failed:', error)
  })

 @ end(fn)

    - Completes any queue operations, disconnect and close the electron process.

   

API List - end

// example1.test.js

// const Nightmare = require('nightmare')
// const nightmare = Nightmare({ show: true })

/*
  @ name goto(url, [headers])
  @ return {
     url: The URL that was loaded
     code: The HTTP status code (e.g. 200, 404, 500)
     method: The HTTP method used (e.g. "GET", "POST")
     referrer: The page that the window was displaying prior to this load or an empty string if this is the first page load.
     headers: An object representing the response headers for the request as in 
              {header1-name: header1-value, header2-name: header2-value}
    }
*/
nightmare
//  .goto('https://duckduckgo.com') 
//  .type('#search_form_input_homepage', 'github nightmare')
//  .click('#search_button_homepage')
//  .wait('#r1-0 a.result__a')
//  .evaluate(() => document.querySelector('#r1-0 a.result__a').href)
  .end()
  .then(console.log)
//  .catch(error => {
//    console.error('Search failed:', error)
  })

.then() must be called after .end() to run the .end() task.

API List - end

// example1.test.js

// const Nightmare = require('nightmare')
// const nightmare = Nightmare({ show: true })

/*
  @ name goto(url, [headers])
  @ return {
     url: The URL that was loaded
     code: The HTTP status code (e.g. 200, 404, 500)
     method: The HTTP method used (e.g. "GET", "POST")
     referrer: The page that the window was displaying prior to this load or an empty string if this is the first page load.
     headers: An object representing the response headers for the request as in 
              {header1-name: header1-value, header2-name: header2-value}
    }
*/
nightmare
//  .goto('https://duckduckgo.com') 
//  .type('#search_form_input_homepage', 'github nightmare')
//  .click('#search_button_homepage')
//  .wait('#r1-0 a.result__a')
//  .evaluate(() => document.querySelector('#r1-0 a.result__a').href)
  .end(rs => {
   console.log('end rs', rs) //  rs: segmentio/nightmare
   return rs + '123'
  })
  .then(console.log)  // output: segmentio/nightmare123
//  .catch(error => {
//    console.error('Search failed:', error)
  })

 if using an .end() callback, the .end() call is equivalent to calling .end() followed by .then(fn)

API List - evaluate

// example1.test.js

// const Nightmare = require('nightmare')
// const nightmare = Nightmare({ show: true })

/*
  @ name goto(url, [headers])
  @ return {
     url: The URL that was loaded
     code: The HTTP status code (e.g. 200, 404, 500)
     method: The HTTP method used (e.g. "GET", "POST")
     referrer: The page that the window was displaying prior to this load or an empty string if this is the first page load.
     headers: An object representing the response headers for the request as in 
              {header1-name: header1-value, header2-name: header2-value}
    }
*/
nightmare
//  .goto('https://duckduckgo.com') 
//  .type('#search_form_input_homepage', 'github nightmare')
//  .click('#search_button_homepage')
//  .wait('#r1-0 a.result__a')
  .evaluate(() => document.querySelector('#r1-0 a.result__a').href)
//  .end()
  .then(console.log)
  .catch(error => {
    console.error('Search failed:', error)
  })

 @ evaluate(fn[, arg1, arg2,...]

    - Useful for extracting information from the page.

    - fn creates a the browser env, all web apis  can be used in the function.

    - Like promise,  use .then() to get the value return from fn, and use .catch() to get error message.

API List - evaluate

/* example3.test.js */
//  Get the text content of H1 element
/*
const Nightmare = require('nightmare')
const nightmare = Nightmare({ show: true })
*/
const selector2 = 'h2'
/*
const selector = 'h1'
nightmare
  .goto('https://github.com/segmentio/nightmare')
*/
  .evaluate(function (selector) {
      // now we're executing inside the browser scope.
      // return document.querySelector(selector).innerText
      return selector2
  }, selector)  // <-- that's how you pass parameters from Node scope to browser scope
//  .end()
  .then(rs => {
    console.log(rs)  // undefined
  }).catch(error => {
    console.error('Search failed:', error)
  })

Search failed: { ReferenceError: selector2 is not defined ... }

Bad case:

  • The function is a independent scope, you can't retrieve the parameter outside the function

 selector2 isn't defined in this scope

API List - evaluate

/* example3.test.js */
//  Get the text content of H1 element
/*
const Nightmare = require('nightmare')
const nightmare = Nightmare({ show: true })
const selector = 'h1'
*/
nightmare
//  .goto('https://github.com/segmentio/nightmare')
//  .evaluate(function (selector) {
      // now we're executing inside the browser scope.
//      return document.querySelector(selector).innerText
//  }, selector)  // <-- that's how you pass parameters from Node scope to browser scope
  .end()
  .then(rs => {
    console.log(rs)  // undefined
  })
//.catch(error => {
//  console.error('Search failed:', error)
//})

.then() must be called after .end() to run the .end() task.

Can I Use ES7 Async/Await?

API List - evaluate

/* example3.test.js */

const Nightmare = require('nightmare')
const nightmare = Nightmare({ show: true })

const selector = 'h1'
async function asynchronous(params) {
  try {
  const rs = await nightmare
    .goto('https://github.com/segmentio/nightmare')
    .evaluate(function (selector) {
        // now we're executing inside the browser scope.
        return document.querySelector(selector).innerText
    }, selector)  // <-- that's how you pass parameters from Node scope to browser scope
    .end()
    // nightmare
  } catch (error) {
    console.log(error)
  }
}

asynchronous()

Yes,  but the Node.js version should at least 7.10.1+

Debugging

Debug Flags
  • DEBUG=nightmare* (All nightmare messages)
  • DEBUG=nightmare:actions* (Only actions)
  • DEBUG=nightmare:log* (Only logs)

LOG MODE

Install cross-env

  npm install cross-env --save  //local
  npm install cross-env -g --save //global
 cross-env DEBUG=nightmare:actions* node [entry file]

Run

LOG MODE

With 'DEBUG=nightmare:actions*'

Without 'DEBUG=nightmare:actions*'

Test Report Library

mochawesome

About mochawesome
  • A report-generator  for mocha
  • Generate a standalone HTML/CSS
  • Runs on Node.js (>=4)
Installation

Install mochawesome

  npm install mochawesome  --save  //local
  npm install mochawesome -g --save //global
mocha [file.js] --reporter mochawesome

Run

Result

source: https://www.npmjs.com/package/mochawesome

References
  • https://www.linode.com/docs/development/nodejs/use-nightmarejs-to-automate-headless-browsing/

  • https://github.com/segmentio/nightmare

  • https://node.green/

Introduction of Nightmare.js

By ceall

Introduction of Nightmare.js

  • 803