Introduction of Nightmare.js
What is Nightmare.js
A high-level browser library
More about Nightmare.js
- Support headless browser
- Wrapped around Electron
- Desktop application framework
- Based on Nodejs and Chromium
- Similar to PhantomJS but roughly twice as fast
- Most often used for UI testing and crawling.
- Over 55k downloads per month ( ~2018/11)
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
- .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
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