Nightmare is lots of fun. It's a browser automation library built around electron with a smooth API. It's titillating to write a few commands in node and watch them be executed in a browser window. Nightmare is used for prototyping, testing, web scraping, and automating tasks.
There are a few things that you should be aware of from the get-go when using nightmare for a more fun developer experience.
var Nightmare = require('nightmare')
var nightmare = Nightmare({ show: true })
var result = nightmare
.goto('http://www.example.com')
.wait(1000)
In the above example, result is a promise.
In nightmare, exists(), visible(), and evaluate() cannot be chained. These methods return promises that must be awaited.
Doesn't work:
nightmare.goto('http://www.example.com').exists('#Btn').wait(1000)
The exists() method checks whether the element exists on the page and returns true or false.
Works:
nightmare.goto('http://www.example.com').exists().then(exists => { console.log(exists) })
With long nightmare sequences, async/await comes in handy. Let's say you need to login to Wordpress:
const getSum = text => {
var arr = text.split('+')
var first = arr[0].replace(/[^\d.]/g, '');
var second = arr[1].replace(/[^\d.]/g, '');
return parseInt(first) + parseInt(second)
}
async function login() {
var text = await nightmare
.goto(`${process.env.BASE_URL}/wp-admin`)
.evaluate(() =>
return document.querySelector("form#loginform div").innerText;
});
var sum = getSum(text);
var cookies = await nightmare
.type("input#user_login", process.env.LOGIN)
.type("input#user_pass", process.env.PASSWORD)
.type('form#loginform div input[type="input"]', sum)
.wait(500)
.click("p.submit input#wp-submit")
.wait(500)
.wait('header.fl-page-header')
.cookies.get();
return cookies.find(cookie => cookie.secure === true && cookie.name !== 'wordpress_test_cookie')
}
In the above example, we send our electron instance to my-wordpress-site.com/wp-admin and attempt to login. But because we need to get the value of form#loginform div before proceeding, we need to break things up with async/await.
Normally the enclosing scope is included:
const tree = 'spruce'
const getTree = () => {
// getTree has access to enclosing scope
return tree
}
//getTree() = spruce
With evaluate() let's us switch to the client-side browser context. But you need to pass all variables explicitly.
Won't work:
const tree = 'spruce'
nightmare.evaluate(() => {
// evaluate() doesn't have access to enclosing scope
document.querySelector("input").value = tree
})
Works:
const tree = 'spruce'
nightmare.evaluate((tree) => {
document.querySelector("input").value = tree
}, tree)
You can inject scripts (e.g., jQuery) with nightmare, but remember to inject prior to calling goto().
nightmare.inject('js', 'node_modules/jquery/dist/jquery.js')
.wait(1000)
.goto('http://www.example.com')
.wait(1000)
Here are some useful recipes. I hope they come in handy!
const scrollToElement = (selector) => {
return nightmare.evaluate(selector => {
document.getElementById(selector).getBoundingClientRect()
var rect = document.getElementById(selector).getBoundingClientRect();
var y = ((rect.bottom - rect.top) / 2) + rect.top;
var x = ((rect.right - rect.left) / 2) + rect.left;
window.scrollTo(x, y)
}, selector)
}
// Use
nightmare.goto('http://www.example.com')
.scrollToElement('#some_element')
.then(() => nightmare.end())
function zoom(n) {
return nightmare
.evaluate(n => {
return document.body.style.zoom = `${n * 100}%`
}, n)
}
await nightmare.zoom(0.5)