Tips and Tricks For Using NightmareJs

May 1, 2018

What's NightmareJs

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.

Gotchas

There are a few things that you should be aware of from the get-go when using nightmare for a more fun developer experience.

Nightmare Sequences Return A Promise

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.

Nightmare Plays Well With Async/Await Syntax

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.

Pay Attention To evaluate() Scope

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)

Injecting Scrips

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)

Useful Recipes

Here are some useful recipes. I hope they come in handy!

Scroll to a Given Element

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())

Zoom In or Out

function zoom(n) {
  return nightmare
  .evaluate(n => {
    return document.body.style.zoom = `${n * 100}%`
  }, n)
}
await nightmare.zoom(0.5)