Git Product home page Git Product logo

nightmare's Introduction

NOTICE: This library is no longer maintained.

Build Status Join the chat at https://gitter.im/rosshinkley/nightmare

Nightmare

Nightmare is a high-level browser automation library from Segment.

The goal is to expose a few simple methods that mimic user actions (like goto, type and click), with an API that feels synchronous for each block of scripting, rather than deeply nested callbacks. It was originally designed for automating tasks across sites that don't have APIs, but is most often used for UI testing and crawling.

Under the covers it uses Electron, which is similar to PhantomJS but roughly twice as fast and more modern.

⚠️ Security Warning: We've implemented many of the security recommendations outlined by Electron to try and keep you safe, but undiscovered vulnerabilities may exist in Electron that could allow a malicious website to execute code on your computer. Avoid visiting untrusted websites.

🛠 Migrating to 3.x: You'll want to check out this issue before upgrading. We've worked hard to make improvements to nightmare while limiting the breaking changes and there's a good chance you won't need to do anything.

Niffy is a perceptual diffing tool built on Nightmare. It helps you detect UI changes and bugs across releases of your web app.

Daydream is a complementary chrome extension built by @stevenmiller888 that generates Nightmare scripts for you while you browse.

Many thanks to @matthewmueller and @rosshinkley for their help on Nightmare.

Examples

Let's search on DuckDuckGo:

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

You can run this with:

npm install --save nightmare
node example.js

Or, let's run some mocha tests:

const Nightmare = require('nightmare')
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()
      })
  })
})

You can see examples of every function in the tests here.

To get started with UI Testing, check out this quick start guide.

To install dependencies

npm install

To run the mocha tests

npm test

Node versions

Nightmare is intended to be run on NodeJS 4.x or higher.

API

Nightmare(options)

Creates a new instance that can navigate around the web. The available options are documented here, along with the following nightmare-specific options.

waitTimeout (default: 30s)

Throws an exception if the .wait() didn't return true within the set timeframe.

const nightmare = Nightmare({
  waitTimeout: 1000 // in ms
})
gotoTimeout (default: 30s)

Throws an exception if the .goto() didn't finish loading within the set timeframe. Note that, even though goto normally waits for all the resources on a page to load, a timeout exception is only raised if the DOM itself has not yet loaded.

const nightmare = Nightmare({
  gotoTimeout: 1000 // in ms
})
loadTimeout (default: infinite)

Forces Nightmare to move on if a page transition caused by an action (eg, .click()) didn't finish within the set timeframe. If loadTimeout is shorter than gotoTimeout, the exceptions thrown by gotoTimeout will be suppressed.

const nightmare = Nightmare({
  loadTimeout: 1000 // in ms
})
executionTimeout (default: 30s)

The maximum amount of time to wait for an .evaluate() statement to complete.

const nightmare = Nightmare({
  executionTimeout: 1000 // in ms
})
paths

The default system paths that Electron knows about. Here's a list of available paths: https://github.com/atom/electron/blob/master/docs/api/app.md#appgetpathname

You can overwrite them in Nightmare by doing the following:

const nightmare = Nightmare({
  paths: {
    userData: '/user/data'
  }
})
switches

The command line switches used by the Chrome browser that are also supported by Electron. Here's a list of supported Chrome command line switches: https://github.com/atom/electron/blob/master/docs/api/chrome-command-line-switches.md

const nightmare = Nightmare({
  switches: {
    'proxy-server': '1.2.3.4:5678',
    'ignore-certificate-errors': true
  }
})
electronPath

The path to the prebuilt Electron binary. This is useful for testing on different versions of Electron. Note that Nightmare only supports the version on which this package depends. Use this option at your own risk.

const nightmare = Nightmare({
  electronPath: require('electron')
})
dock (OS X)

A boolean to optionally show the Electron icon in the dock (defaults to false). This is useful for testing purposes.

const nightmare = Nightmare({
  dock: true
})
openDevTools

Optionally shows the DevTools in the Electron window using true, or use an object hash containing mode: 'detach' to show in a separate window. The hash gets passed to contents.openDevTools() to be handled. This is also useful for testing purposes. Note that this option is honored only if show is set to true.

const nightmare = Nightmare({
  openDevTools: {
    mode: 'detach'
  },
  show: true
})
typeInterval (default: 100ms)

How long to wait between keystrokes when using .type().

const nightmare = Nightmare({
  typeInterval: 20
})
pollInterval (default: 250ms)

How long to wait between checks for the .wait() condition to be successful.

const nightmare = Nightmare({
  pollInterval: 50 //in ms
})
maxAuthRetries (default: 3)

Defines the number of times to retry an authentication when set up with .authenticate().

const nightmare = Nightmare({
  maxAuthRetries: 3
})

certificateSubjectName

A string to determine the client certificate selected by electron. If this options is set, the select-client-certificate event will be set to loop through the certificateList and find the first certificate that matches subjectName on the electron Certificate Object.

const nightmare = Nightmare({
  certificateSubjectName: 'tester'
})

.engineVersions()

Gets the versions for Electron and Chromium.

.useragent(useragent)

Sets the useragent used by electron.

.authentication(user, password)

Sets the user and password for accessing a web page using basic authentication. Be sure to set it before calling .goto(url).

.end()

Completes any queue operations, disconnect and close the electron process. Note that if you're using promises, .then() must be called after .end() to run the .end() task. Also note that if using an .end() callback, the .end() call is equivalent to calling .end() followed by .then(fn). Consider:

nightmare
  .goto(someUrl)
  .end(() => 'some value')
  //prints "some value"
  .then(console.log)

.halt(error, done)

Clears all queued operations, kills the electron process, and passes error message or 'Nightmare Halted' to an unresolved promise. Done will be called after the process has exited.

Interact with the Page

.goto(url[, headers])

Loads the page at url. Optionally, a headers hash can be supplied to set headers on the goto request.

When a page load is successful, goto returns an object with metadata about the page load, including:

  • 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}

If the page load fails, the error will be an object with the following properties:

Note that any valid response from a server is considered “successful.” That means things like 404 “not found” errors are successful results for goto. Only things that would cause no page to appear in the browser window, such as no server responding at the given address, the server hanging up in the middle of a response, or invalid URLs, are errors.

You can also adjust how long goto will wait before timing out by setting the gotoTimeout option on the Nightmare constructor.

.back()

Goes back to the previous page.

.forward()

Goes forward to the next page.

.refresh()

Refreshes the current page.

.click(selector)

Clicks the selector element once.

.mousedown(selector)

Mousedowns the selector element once.

.mouseup(selector)

Mouseups the selector element once.

.mouseover(selector)

Mouseovers the selector element once.

.mouseout(selector)

Mouseout the selector element once.

.type(selector[, text])

Enters the text provided into the selector element. Empty or falsey values provided for text will clear the selector's value.

.type() mimics a user typing in a textbox and will emit the proper keyboard events.

Key presses can also be fired using Unicode values with .type(). For example, if you wanted to fire an enter key press, you would write .type('body', '\u000d').

If you don't need the keyboard events, consider using .insert() instead as it will be faster and more robust.

.insert(selector[, text])

Similar to .type(), .insert() enters the text provided into the selector element. Empty or falsey values provided for text will clear the selector's value.

.insert() is faster than .type() but does not trigger the keyboard events.

.check(selector)

Checks the selector checkbox element.

.uncheck(selector)

Unchecks the selector checkbox element.

.select(selector, option)

Changes the selector dropdown element to the option with attribute [value=option]

.scrollTo(top, left)

Scrolls the page to desired position. top and left are always relative to the top left corner of the document.

.viewport(width, height)

Sets the viewport size.

.inject(type, file)

Injects a local file onto the current page. The file type must be either js or css.

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

Invokes fn on the page with arg1, arg2,.... All the args are optional. On completion it returns the return value of fn. Useful for extracting information from the page. Here's an example:

const selector = 'h1'
nightmare
  .evaluate(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
  .then(text => {
    // ...
  })

Error-first callbacks are supported as a part of evaluate(). If the arguments passed are one fewer than the arguments expected for the evaluated function, the evaluation will be passed a callback as the last parameter to the function. For example:

const selector = 'h1'
nightmare
  .evaluate((selector, done) => {
    // now we're executing inside the browser scope.
    setTimeout(
      () => done(null, document.querySelector(selector).innerText),
      2000
    )
  }, selector)
  .then(text => {
    // ...
  })

Note that callbacks support only one value argument (eg function(err, value)). Ultimately, the callback will get wrapped in a native Promise and only be able to resolve a single value.

Promises are also supported as a part of evaluate(). If the return value of the function has a then member, .evaluate() assumes it is waiting for a promise. For example:

const selector = 'h1';
nightmare
  .evaluate((selector) => (
    new Promise((resolve, reject) => {
      setTimeout(() => resolve(document.querySelector(selector).innerText), 2000);
    )}, selector)
  )
  .then((text) => {
    // ...
  })

.wait(ms)

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

.wait(selector)

Waits until the element selector is present e.g. .wait('#pay-button').

.wait(fn[, arg1, arg2,...])

Waits until the fn evaluated on the page with arg1, arg2,... returns true. All the args are optional. See .evaluate() for usage.

.header(header, value)

Adds a header override for all HTTP requests. If header is undefined, the header overrides will be reset.

Extract from the Page

.exists(selector)

Returns whether the selector exists or not on the page.

.visible(selector)

Returns whether the selector is visible or not.

.on(event, callback)

Captures page events with the callback. You have to call .on() before calling .goto(). Supported events are documented here.

Additional "page" events
.on('page', function(type="error", message, stack))

This event is triggered if any javascript exception is thrown on the page. But this event is not triggered if the injected javascript code (e.g. via .evaluate()) is throwing an exception.

"page" events

Listens for window.addEventListener('error'), alert(...), prompt(...) & confirm(...).

.on('page', function(type="error", message, stack))

Listens for top-level page errors. This will get triggered when an error is thrown on the page.

.on('page', function(type="alert", message))

Nightmare disables window.alert from popping up by default, but you can still listen for the contents of the alert dialog.

.on('page', function(type="prompt", message, response))

Nightmare disables window.prompt from popping up by default, but you can still listen for the message to come up. If you need to handle the confirmation differently, you'll need to use your own preload script.

.on('page', function(type="confirm", message, response))

Nightmare disables window.confirm from popping up by default, but you can still listen for the message to come up. If you need to handle the confirmation differently, you'll need to use your own preload script.

.on('console', function(type [, arguments, ...]))

type will be either log, warn or error and arguments are what gets passed from the console. This event is not triggered if the injected javascript code (e.g. via .evaluate()) is using console.log.

.once(event, callback)

Similar to .on(), but captures page events with the callback one time.

.removeListener(event, callback)

Removes a given listener callback for an event.

.screenshot([path][, clip])

Takes a screenshot of the current page. Useful for debugging. The output is always a png. Both arguments are optional. If path is provided, it saves the image to the disk. Otherwise it returns a Buffer of the image data. If clip is provided (as documented here), the image will be clipped to the rectangle.

.html(path, saveType)

Saves the current page as html as files to disk at the given path. Save type options are here.

.pdf(path, options)

Saves a PDF to the specified path. Options are here.

.title()

Returns the title of the current page.

.url()

Returns the url of the current page.

.path()

Returns the path name of the current page.

Cookies

.cookies.get(name)

Gets a cookie by it's name. The url will be the current url.

.cookies.get(query)

Queries multiple cookies with the query object. If a query.name is set, it will return the first cookie it finds with that name, otherwise it will query for an array of cookies. If no query.url is set, it will use the current url. Here's an example:

// get all google cookies that are secure
// and have the path `/query`
nightmare
  .goto('http://google.com')
  .cookies.get({
    path: '/query',
    secure: true
  })
  .then(cookies => {
    // do something with the cookies
  })

Available properties are documented here: https://github.com/atom/electron/blob/master/docs/api/session.md#sescookiesgetdetails-callback

.cookies.get()

Gets all the cookies for the current url. If you'd like get all cookies for all urls, use: .get({ url: null }).

.cookies.set(name, value)

Sets a cookie's name and value. This is the most basic form, and the url will be the current url.

.cookies.set(cookie)

Sets a cookie. If cookie.url is not set, it will set the cookie on the current url. Here's an example:

nightmare
  .goto('http://google.com')
  .cookies.set({
    name: 'token',
    value: 'some token',
    path: '/query',
    secure: true
  })
  // ... other actions ...
  .then(() => {
    // ...
  })

Available properties are documented here: https://github.com/atom/electron/blob/master/docs/api/session.md#sescookiessetdetails-callback

.cookies.set(cookies)

Sets multiple cookies at once. cookies is an array of cookie objects. Take a look at the .cookies.set(cookie) documentation above for a better idea of what cookie should look like.

.cookies.clear([name])

Clears a cookie for the current domain. If name is not specified, all cookies for the current domain will be cleared.

nightmare
  .goto('http://google.com')
  .cookies.clear('SomeCookieName')
  // ... other actions ...
  .then(() => {
    // ...
  })

.cookies.clearAll()

Clears all cookies for all domains.

nightmare
  .goto('http://google.com')
  .cookies.clearAll()
  // ... other actions ...
  .then(() => {
    //...
  })

Proxies

Proxies are supported in Nightmare through switches.

If your proxy requires authentication you also need the authentication call.

The following example not only demonstrates how to use proxies, but you can run it to test if your proxy connection is working:

import Nightmare from 'nightmare';

const proxyNightmare = Nightmare({
  switches: {
    'proxy-server': 'my_proxy_server.example.com:8080' // set the proxy server here ...
  },
  show: true
});

proxyNightmare
  .authentication('proxyUsername', 'proxyPassword') // ... and authenticate here before `goto`
  .goto('http://www.ipchicken.com')
  .evaluate(() => {
    return document.querySelector('b').innerText.replace(/[^\d\.]/g, '');
  })
  .end()
  .then((ip) => { // This will log the Proxy's IP
    console.log('proxy IP:', ip);
  });

// The rest is just normal Nightmare to get your local IP
const regularNightmare = Nightmare({ show: true });

regularNightmare
  .goto('http://www.ipchicken.com')
  .evaluate(() =>
    document.querySelector('b').innerText.replace(/[^\d\.]/g, '');
  )
  .end()
  .then((ip) => { // This will log the your local IP
    console.log('local IP:', ip);
  });

Promises

By default, Nightmare uses default native ES6 promises. You can plug in your favorite ES6-style promises library like bluebird or q for convenience!

Here's an example:

var Nightmare = require('nightmare')

Nightmare.Promise = require('bluebird')
// OR:
Nightmare.Promise = require('q').Promise

You can also specify a custom Promise library per-instance with the Promise constructor option like so:

var Nightmare = require('nightmare')

var es6Nightmare = Nightmare()
var bluebirdNightmare = Nightmare({
  Promise: require('bluebird')
})

var es6Promise = es6Nightmare
  .goto('https://github.com/segmentio/nightmare')
  .then()
var bluebirdPromise = bluebirdNightmare
  .goto('https://github.com/segmentio/nightmare')
  .then()

es6Promise.isFulfilled() // throws: `TypeError: es6EndPromise.isFulfilled is not a function`
bluebirdPromise.isFulfilled() // returns: `true | false`

Extending Nightmare

Nightmare.action(name, [electronAction|electronNamespace], action|namespace)

You can add your own custom actions to the Nightmare prototype. Here's an example:

Nightmare.action('size', function(done) {
  this.evaluate_now(() => {
    const w = Math.max(
      document.documentElement.clientWidth,
      window.innerWidth || 0
    )
    const h = Math.max(
      document.documentElement.clientHeight,
      window.innerHeight || 0
    )
    return {
      height: h,
      width: w
    }
  }, done)
})

Nightmare()
  .goto('http://cnn.com')
  .size()
  .then(size => {
    //... do something with the size information
  })

Remember, this is attached to the static class Nightmare, not the instance.

You'll notice we used an internal function evaluate_now. This function is different than nightmare.evaluate because it runs it immediately, whereas nightmare.evaluate is queued.

An easy way to remember: when in doubt, use evaluate. If you're creating custom actions, use evaluate_now. The technical reason is that since our action has already been queued and we're running it now, we shouldn't re-queue the evaluate function.

We can also create custom namespaces. We do this internally for nightmare.cookies.get and nightmare.cookies.set. These are useful if you have a bundle of actions you want to expose, but it will clutter up the main nightmare object. Here's an example of that:

Nightmare.action('style', {
  background(done) {
    this.evaluate_now(
      () => window.getComputedStyle(document.body, null).backgroundColor,
      done
    )
  }
})

Nightmare()
  .goto('http://google.com')
  .style.background()
  .then(background => {
    // ... do something interesting with background
  })

You can also add custom Electron actions. The additional Electron action or namespace actions take name, options, parent, win, renderer, and done. Note the Electron action comes first, mirroring how .evaluate() works. For example:

Nightmare.action(
  'clearCache',
  (name, options, parent, win, renderer, done) => {
    parent.respondTo('clearCache', done => {
      win.webContents.session.clearCache(done)
    })
    done()
  },
  function(done) {
    this.child.call('clearCache', done)
  }
)

Nightmare()
  .clearCache()
  .goto('http://example.org')
  //... more actions ...
  .then(() => {
    // ...
  })

...would clear the browser’s cache before navigating to example.org.

See this document for more details on creating custom actions.

.use(plugin)

nightmare.use is useful for reusing a set of tasks on an instance. Check out nightmare-swiftly for some examples.

Custom preload script

If you need to do something custom when you first load the window environment, you can specify a custom preload script. Here's how you do that:

import path from 'path'

const nightmare = Nightmare({
  webPreferences: {
    preload: path.resolve('custom-script.js')
    //alternative: preload: "absolute/path/to/custom-script.js"
  }
})

The only requirement for that script is that you'll need the following prelude:

window.__nightmare = {}
__nightmare.ipc = require('electron').ipcRenderer

To benefit of all of nightmare's feedback from the browser, you can instead copy the contents of nightmare's preload script.

Storage Persistence between nightmare instances

By default nightmare will create an in-memory partition for each instance. This means that any localStorage or cookies or any other form of persistent state will be destroyed when nightmare is ended. If you would like to persist state between instances you can use the webPreferences.partition api in electron.

import Nightmare from 'nightmare';

nightmare = Nightmare(); // non persistent paritition by default
yield nightmare
  .evaluate(() => {
    window.localStorage.setItem('testing', 'This will not be persisted');
  })
  .end();

nightmare = Nightmare({
  webPreferences: {
    partition: 'persist: testing'
  }
});
yield nightmare
  .evaluate(() => {
    window.localStorage.setItem('testing', 'This is persisted for other instances with the same paritition name');
  })
  .end();

If you specify a null paritition then it will use the electron default behavior (persistent) or any string that starts with 'persist:' will persist under that partition name, any other string will result in in-memory only storage.

Usage

Installation

Nightmare is a Node.js module, so you'll need to have Node.js installed. Then you just need to npm install the module:

$ npm install --save nightmare

Execution

Nightmare is a node module that can be used in a Node.js script or module. Here's a simple script to open a web page:

import Nightmare from 'nightmare';

const nightmare = Nightmare();

nightmare.goto('http://cnn.com')
  .evaluate(() => {
    return document.title;
  })
  .end()
  .then((title) => {
    console.log(title);
  })

If you save this as cnn.js, you can run it on the command line like this:

npm install --save nightmare
node cnn.js

Common Execution Problems

Nightmare heavily relies on Electron for heavy lifting. And Electron in turn relies on several UI-focused dependencies (eg. libgtk+) which are often missing from server distros.

For help running nightmare on your server distro check out How to run nightmare on Amazon Linux and CentOS guide.

Debugging

There are three good ways to get more information about what's happening inside the headless browser:

  1. Use the DEBUG=* flag described below.
  2. Pass { show: true } to the nightmare constructor to have it create a visible, rendered window where you can watch what is happening.
  3. Listen for specific events.

To run the same file with debugging output, run it like this DEBUG=nightmare node cnn.js (on Windows use set DEBUG=nightmare & node cnn.js).

This will print out some additional information about what's going on:

nightmare queueing action "goto" +0ms
nightmare queueing action "evaluate" +4ms
Breaking News, U.S., World, Weather, Entertainment & Video News - CNN.com
Debug Flags

All nightmare messages

DEBUG=nightmare*

Only actions

DEBUG=nightmare:actions*

Only logs

DEBUG=nightmare:log*

Additional Resources

Tests

Automated tests for nightmare itself are run using Mocha and Chai, both of which will be installed via npm install. To run nightmare's tests, just run make test.

When the tests are done, you'll see something like this:

make test
  ․․․․․․․․․․․․․․․․․․
  18 passing (1m)

Note that if you are using xvfb, make test will automatically run the tests under an xvfb-run wrapper. If you are planning to run the tests headlessly without running xvfb first, set the HEADLESS environment variable to 0.

License (MIT)

WWWWWW||WWWWWW
 W W W||W W W
      ||
    ( OO )__________
     /  |           \
    /o o|    MIT     \
    \___/||_||__||_|| *
         || ||  || ||
        _||_|| _||_||
       (__|__|(__|__|

Copyright (c) 2015 Segment.io, Inc. mailto:[email protected]

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

nightmare's People

Contributors

38elements avatar agrberg avatar azurelogic avatar dfrankland avatar dominicbarnes avatar emars avatar fritx avatar imsky avatar inversion avatar itsjustcon avatar jekku avatar juanpaulo avatar justinmchase avatar lambtron avatar linusu avatar matthewmueller avatar mr0grog avatar ocombe avatar oliviervaillancourt avatar queckezz avatar reinpk avatar renruyi avatar rfink avatar rhysd avatar rosshinkley avatar rstacruz avatar securingsincity avatar stephenmathieson avatar tejasmanohar avatar yoz avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

nightmare's Issues

viewport parameters don't seem to work

I promise I'm not going to open a billion issues.

I'm tying to set viewport and take a screenshot with:

var Nightmare = require('nightmare');
new Nightmare()
  .goto('http://yahoo.com')
  .type('input[title="Search"]', 'github nightmare')
  .click('.searchsubmit')
  .viewport(320, 480)
  .screenshot('yahoo-320.png')
  .viewport(1000, 480)
  .screenshot('yahoo-1000.png')
  .run(function (err, nightmare) {
    if (err) return console.log(err);
    console.log('Done!');
  });

The resulting yahoo-320.png and yahoo-1000.png have no diff. I tried with viewport params as integers and strings. Same result.

Don't precise that you need brew updated in setup process

Brew is only for mac. You're not alone in this world. Some(lot) of us are on pc or just debian, ubuntu and other great OS. We don't need to know you've a Mac.

Sorry for saying this like this, but it's really annoying to see a lot of people thinking that all of developers are on mac. It's not true.

phantom stdout: TypeError: undefined is not an object (evaluating 'phantom.args[0]')

Running into this when trying to run the examples:

  nightmare queueing action "evaluate" +0ms
  nightmare run +5ms
  nightmare .setup() creating phantom instance on port 12301 +1ms
phantom stdout: TypeError: undefined is not an object (evaluating 'phantom.args[0]')


phantom stdout:   /Users/matt/Playground/node_modules/nightmare/node_modules/phantom/shim.js:5421
  /Users/matt/Playground/node_modules/nightmare/node_modules/phantom/shim.js:5599
  /Users/matt/Playground/node_modules/nightmare/node_modules/phantom/shim.js:159
  /Users/matt/Playground/node_modules/nightmare/node_modules/phantom/shim.js:11 in require

This could definitely be an issue with phantomjs on 10.10, but any ideas?

How do I run say a nightmare with login, then start a loop and then pick up where I left off?

I'm just writing to ask if there is a simple way I can accomplish this.

I want to know if I can run a part of a nightmare, then stop and do whatever I like...say start a for loop, and then within that loop start up some more of the nightmare where the nightmare left off. As in, still logged in that is. Then I'd run a little more chunk of nightmare code, and then upon the next iteration begin some more nightmare code....all on the same login.

I'll try and write some psuedo-code to help explain it.

Nightmare = req (nightmare)

var myArray = [.., .., .., ..];
var newArray = [];
var myNM = new Nightmare

//Initial login with my nightmare.
myNM.goto('yahoo.com')
.type('#login', login)
.type('#pass', pass)
.click('#submit')
.run();

//Stop and begin a for loop.
for (i = 0; i < myArray.length; i++) {
    //Immediately invoke the previous nightmare and start some more code right where it had left off..
    myNM.wait()
    .type('#field1', myArray[i])
    .click('#checkBtn')
    .wait()
    //Give back some data...add it to the newArray for later.
    .evaluate(func() {
        newArray.push(document.querySelector('#result'));
        return document.querySelector('#result');
    }, func(res) { console.log(res); })
    .run()
}

//Print out the contents of the newArray we just built.
console.dir(newArray);

Installation failing on windows 7

I'm trying to hit 'npm install --save nightmare' on a fresh windows install. OS is windows 7 64-bit.

I've installed phantomjs via npm, and I have my PYTHON env variable pointing to python 3.4.

It looks like the 'weak' dependency is failing maybe?

Command line dump:

C:\dev\Hackathon\NightmareJS>npm install --save nightmare

[email protected] install C:\dev\Hackathon\NightmareJS\node_modules\nightmare\node_modules\phantom\node_modules\dnode\node_modules\weak
node-gyp rebuild

C:\dev\Hackathon\NightmareJS\node_modules\nightmare\node_modules\phantom\node_modules\dnode\node_modules\weak>node "C:\Program Files\nodejs\node_modules\npm\bin\node-gyp-bin....\node_modules\node-gyp\bin\node-gyp.js" rebuild
gyp ERR! configure error
gyp ERR! stack Error: spawn ENOENT
gyp ERR! stack at errnoException (child_process.js:1001:11)
gyp ERR! stack at Process.ChildProcess._handle.onexit (child_process.js:792:34)
gyp ERR! System Windows_NT 6.1.7601
gyp ERR! command "node" "C:\Program Files\nodejs\node_modules\npm\node_modules\node-gyp\bin\node-gyp.js" "rebuild"
gyp ERR! cwd C:\dev\Hackathon\NightmareJS\node_modules\nightmare\node_modules\phantom\node_modules\dnode\node_modules\weak
gyp ERR! node -v v0.10.32
gyp ERR! node-gyp -v v1.0.1
gyp ERR! not ok
npm WARN optional dep failed, continuing [email protected]
[email protected] node_modules\nightmare
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
└── [email protected] ([email protected], [email protected], [email protected], [email protected])

Are there any additional dependencies I'm missing perhaps?

add .use to queue

Let's say I have a simple search task:

new Nightmare()
  .use(tw.login('user', 'pw')
  .use(tw.search('503'))
  .use(function plugin () {
    return function (nightmare) {
      // function gets executed immediately, before tw.search() and tw.login() are done.
    }
  })

The return function get's executed immediately after nightmare is instantiated. If search() returns some properties that I want to use for plugin(), they would be undefined since the function was executed before the plugin.

Not sure if this can be done and if I'm missing something but the .use() function could be added to the queue so that all the other tasks before finishes first.

automatically run a singleton server that can relay live screenshots

It's nearly impossible to debug automation scripts without seeing what's going on. So far I've been lamely scp-ing screenshots from .screen() calls back to my local machine and then inspecting.

The idea here would be to have a singleton server with a simple page showing a list of running Nightmare instances. Clicking an instance would let you observe the instance's behavior in close to real time, with the instance adding a screenshot step in between every action. When the viewer disconnected it would automatically stop screenshotting.

google example is failing

Cloned project. cd to project. npm install. created a google.js with:

var Nightmare = require('nightmare');
new Nightmare()
  .goto('https://google.com')
    .type('input#gbqfq', 'github nightmare')
    .click('button#gbqfba')
    .run(function(err, nightmare) {
      if (err) return fn(err);
      fn();
    });

tried running node google.js

Error:

stream.js:94
      throw er; // Unhandled stream error in pipe.
            ^
ReferenceError: fn is not defined
    at /Users/dstanton/Sites/_projects/nightmarejs/nightmare/google.js:9:7
    at next (/Users/dstanton/Sites/node_modules/nightmare/lib/index.js:59:34)
    at f (/Users/dstanton/Sites/node_modules/nightmare/node_modules/once/once.js:16:25)
    at Proto.apply (/Users/dstanton/Sites/node_modules/nightmare/node_modules/phantom/node_modules/dnode/node_modules/dnode-protocol/index.js:123:13)
    at Proto.handle (/Users/dstanton/Sites/node_modules/nightmare/node_modules/phantom/node_modules/dnode/node_modules/dnode-protocol/index.js:99:19)
    at D.dnode.handle (/Users/dstanton/Sites/node_modules/nightmare/node_modules/phantom/node_modules/dnode/lib/dnode.js:140:21)
    at D.dnode.write (/Users/dstanton/Sites/node_modules/nightmare/node_modules/phantom/node_modules/dnode/lib/dnode.js:128:22)
    at SockJSConnection.ondata (stream.js:51:26)
    at SockJSConnection.emit (events.js:95:17)
    at Session.didMessage (/Users/dstanton/Sites/node_modules/nightmare/node_modules/phantom/node_modules/shoe/node_modules/sockjs/lib/transport.js:220:25)

Breaking the chain

I was wondering your thoughts on adding a method to break out of the nightmare chain, so that you can execute things in Node's context. I'm thinking of an action like this:

exports.node = function( callback, done ){
  callback( this, done );
};

which would allow something like this in the nightmare chain:

nightmare
    .goto("http://www.google.com")
    .node( function( n, done ){
        n.screenshot("out.png");
        ///image processing in the local filesystem
        request.post(/* send the processed file somewhere */, function(){
            done();
        });
    })
    //continue the chain
    .run();

You can accomplish nearly the same thing with .evaluate, but this is more explicit and gives you access to the nightmare instance.

ability to change the user agent

Would be awesome if we could add an option to change the userAgent.

Maybe we could add it as an optional parameter to the .goto function.

new Nightmare()
  .goto(agent, 'http://site.com')
  .goto('http://example.com')

But it might be better to set something like this global and add it to the options object for nightmare

new Nightmare({
    agent: 'Chrome ...'
  })
  .goto('http://example.com')

you can modify the user agent in phantomjs from page.settings.userAgent.

btw, great libary! So much easier to work with, especially without the callback hell and those unnecessary complex function names :p

can we have more examples?

Love the tool, definetely better than casperjs.
Can we have more examples such as but not limited to :
--how to open and post to multiple urls
--dynamic loading,lazy-loading
--javascript injection

Thank you 👍

add support for Phantom 2

I have a fork of phantomjs-node that supports PhantomJS 2, and I am trying to get it accepted upstream. The changes so far look pretty clean and are backwards compatible with PhantomJS 1.9.7, so hopefully that goes smoothly. In the meantime, people that want to give Phantom 2 a spin should be able to replace the nightmare version in their package.json with "https://github.com/bprodoehl/nightmare/archive/phantom2.tar.gz"

The PR for phantomjs-node is here: amir20/phantomjs-node#209

queue order not quite right

Great work on the latest commits, much nicer now :) (Also I noticed some speed improvements since you changed to phantomjs-node)

There is one problem with the .run() method though:

new Nightmare()
    .goto('http://yahoo.com')
    .evaluate(function () {
      return document.title;
    }, function (title) {
      console.log('1')
    })
    .run(function (err, nightmare) {
      console.log('2');
    });

then the output would be:

  2
  1

which doesn't make sense, right? It should first report the 1 and then it should invoke the callback from .run(). Right now it's the opposite. Couldn't figure out why though.

Quick fix is to throw a .wait(500) in between. But this gets quite hard to guess the time it takes for .evaluate() to finish especially if it does not always have the same execution time.

add some sort of metadata

A cool idea might be to add the ability for each plugin to append metadata to the nightmare instance. I know that metalsmith does the exact same thing. It would be incredibly helpful when you try to communicate with those different plugins and easier for testing.

Nightmare()
  .use(plugin())
  .run(function () {
    var val = nightmare.metadata.value
    assert(val, 'test')
  })

function plugin () {
  return function (nightmare) {
    nightmare.metadata.value = 'test'
  }
}

won't be too difficult to implement. I can make a PR if this make sense to add in.

.wait(selector) does not work

var Nightmare = require('nightmare');
var nightmare= new Nightmare();

nightmare
    .goto("http://www.google.com")
    .wait("input")
    .run(function( err ){
        if ( err ) throw err;
    });

results in

phantom stdout: SYNTAX_ERR: DOM Exception 12: An invalid or illegal string was specified.

phantom stdout:   phantomjs://webpage.evaluate():2
  phantomjs://webpage.evaluate():8
  phantomjs://webpage.evaluate():8

The selector used doesn't seem to matter - it always throws the error.

Web scraper example?

Is nightmare seems like it would be a suitable solution for web scraping. Do you think you could write an example?

Read from file/input?

This is a great little nifty plugin!

I'm wondering if there's any way to get user input while Nightmare is running. I want to be able to pull my bank info automatically, but in order to log in to my bank account I have to:

  1. Go to the login page
  2. Type in the code generated by the website into my physical device (I can see the code by using .screenshot())
  3. My device generates a new code that I type into the website to log in.

The problem is the third step. Is there a way for Nightmare to get some user input AFTER that it has been initiated? Either through stdin, or maybe just by using .wait() so that you have time to edit a textfile that it can later read from?

Thanks!

AJAX Page Updates & Unexpected Behavior - help?

I'm trying to work with nightmare in an "after-login" scenario (already had nightmare do the logging in and that part works great). Only on the page that comes up after the login, it's utilizing some kind of client-side javascript to call back to the server where it checks the submitted form data and then updates the page with the results.

In an actual web browser such as Chrome the page actually does this; however in my nightmare script, the moment I tell it to click the submit button, it just resets the form or something...I end up with nothing but what I started with before entering the data. My screenshots literally show empty form, then typed-in(filled) form, then empty form again after the click.

Am I going about this all wrong? Or is there a real problem here?

Thanks.

process hangs in `run` callback

looks like there is no way of getting out of run method and continue the chain, see the code below..

var Nightmare = require( 'nightmare' );

new Nightmare()
  .goto( './pulse/jasmine/specrunner.html' )
  .run(function() {
    return $( '.alert' ).text();
  }, function( err, res ) {
    console.log( 'test status: ' + ( !!res.match( /passing/i ) ) );
    if( err ) { throw new Error( err ) };
    return;
  })
  .done(function( nightmare ) {
    // <<-- cannot reach here 
    console.log( 'done' );
    process.exit( 0 );
  });

nightmare: command not found

nightmarejs: command not found.

It must work a little differently than phantomjs on the command line. As far as I know I installed it properly.

suppress PhantomJS errors

I got the following error when loading https://github.com/lgersman/pragmatic-angular:

phantom stdout: TypeError: 'undefined' is not a function (evaluating 'Array.prototype.forEach.call.bind(Array.prototype.forEach)')


phantom stdout:   https://assets-cdn.github.com/assets/frameworks-dabc650f8a51dffd1d4376a3522cbda5536e4807e01d2a86ff7e60d8d6ee3029.js:29
  https://assets-cdn.github.com/assets/frameworks-dabc650f8a51dffd1d4376a3522cbda5536e4807e01d2a86ff7e60d8d6ee3029.js:29

phantom stdout: ReferenceError: Can't find variable: $

  https://assets-cdn.github.com/assets/github-fa2f009761e3bc4750ed00845b9717b09646361cbbc3fa473ad64de9ca6ccf5b.js:1
  https://assets-cdn.github.com/assets/github-fa2f009761e3bc4750ed00845b9717b09646361cbbc3fa473ad64de9ca6ccf5b.js:1

Is it true that we can suppress these errors if we add onError handler on PhantomJS?
Currently we cannot add it through Nightmare.

SlimerJS and TrifleJS support via engine option

Any plans to add support for SlimerJS and TrifleJS? I believe it has similar implementation.

Would be nice to see an engine method as an option (defaults to phantomjs).

var Nightmare = require('nightmare');
new Nightmare()
+  .engine('slimerjs')
  .goto('http://kayak.com')
  .evaluate(function () {
    return document.documentElement.innerHTML;
  }, function (res) {
    console.log(res);
  })
  .run();

Thoughts?

Thanks!

Control Flow/Delay Teardown

Hi. Thanks for the neat tool.

I'm trying it out tonight and have hit a roadblock that I can't seem to make it around. I'm looking to add a conditional branch to my pipeline -- i.e. if a certain element is visible on the page, then click it and fill out some form, if that element is not visible then just proceed to whatever else you were going to do.

I can't seem to figure out a way to do this without dropping down to raw JS for that alternate flow in an evaluate -- which kind of defeats the whole purpose of using this abstraction. I thought about adding the control flow in the callback used in run(), but it seems that the nightmare instance returned as the second argument there has already been deconstructed, meaning I can't queue up any further interactions with it.

Are there any examples of this functionality?

Examples of test execution

Disclaimer: I've never worked with PhantomJS before, only with nightwatch.
The README is missing an important line for newcomers-- how to execute the tests?
Also if only node testname.js needs to be executed (I don't know if I'm doing it correctly) no output is shown to track progress/debug.

nightmare.evaluate(fn, cb, [args]) variable/args scope and errors

I'm struggling with this one because it seems like it should just work.

When I've got things set up like so.... it should pass in arg1 and arg2 into the fn on the evaluate when it calls it right? Well, it doesn't seem to. In addition it seems to also throw errors that don't make sense sometimes.

The return statement is where I thought I could return whatever I wanted as a result, but when I have the callback called with res (for the result (what was returned from fn), sometimes it gives me some errors, and sometimes it just does nothing.

[pseudo-code...sort of]

global var1 // foo
global var2 // bar

new Nightmare()
    .blah-blah-blah
    .evaluate(fn(param1, param2){
        some code
        console.log(param1 + " " + param2);  // foo bar
        return "The result is pretty.";
    }, cb(res) { console.log(res); }, ["var1", "var2])
    .run();  // In theory produces the result of calling fn with var1 and var2, ...which would
    // be "foo bar" in the console, along with "The result is pretty."

.type() should actually send keys

Currently type just changes the value property of the element. This doesn't work with libraries that listen for key events. Therefore I suggest to use phantoms page.sendEvent("keypress", keys) for the implementation of type(). I unfortunately can't provide a pull request due to work restrictions.

Use normal ascii quotes and apostrophes in code examples

In the simple api section of the website, the code formatting(?) is converting the double-quotes to &#x201d; (ironically enough, not 201f for the opening quote) and the apostrophes to &#x2018; and &#x2019; in the code snippets.

This stops users from copy-and-pasting the examples into their terminal or their editor, along with looking out of place with the rest of the fixed-width font.

add debugger support please

Could you please add remote-debugger-port and remote-debugger-autorun?

I tried to hack this in myself,

index.js:
if (this.options.debuggerPort !== null) {
flags.push('--remote-debugger-port='+this.options.debuggerPort);
flags.push('--remote-debugger-autorun=yes');
}

But it didn't work, the console complained with the following:

phantom stdout: TypeError: 'undefined' is not a function (evaluating 'core_require('webpage')')

about:blank:5433
about:blank:5642
about:blank:179
about:blank:25
about:blank:5645
:1

Add a mechanism to teardown the Phantom instance

Right now I have to do:

  var nm = new Nightmare()
  nm.setupInstance(function (instance) { instance.exit() })

So my test runner can exit and the Node process can terminate (I know Mocha does some trickery to close stuff for you but other frameworks do not -- for instance tap and tape).

Would it makes sense to either expose the PHANTOM_INSTANCE or add a method to exit?

Example code not working as expected

I grabbed the example code and modified it:

new Nightmare()
    .goto('http://kayak.com')
    .evaluate(function (page) {
        return page;
        // return document.documentElement.innerHTML;
    }, function (res) {
        console.log(page);
        // console.log(res);
    })
    .run();

The output in the terminal is:

>> node test.js

stream.js:94
      throw er; // Unhandled stream error in pipe.
            ^
ReferenceError: page is not defined

ssl sites fail?

'use strict';

var Nm = require('nightmare');
var browser = new Nm();
var events = [ 'initialized', 'loadStarted', 'loadFinished', 'urlChanged', 'navigationRequested', /*'resourceRequested', 'resourceReceived'*/, 'consoleMessage', 'alert', 'confirm', 'prompt' ];

events.forEach(function (event) {
  browser.on(event, function() {
    console.log('event %s: %j', event, arguments);
  });
});

browser
  .useragent('Cheddar Cheese Dip')
  .goto('http://twitter.com')
  .screenshot('twitter_com.jpg')
  .goto('http://httpbin.org')
  .screenshot('httpbin_org.jpg')
  .evaluate(function () {
    return document.title;
  }, function (res) {
    console.log('title: %j', res);
  })
  .goto('http://gfycat.com')
  .screenshot('gfycat_com.jpg')
  .run(function (err) {
    console.log('err:', err);
  });

twitter_com.jpg will become a black image.

Here's a log:

$ node app.js                                                                         
event navigationRequested: {"0":"http://twitter.com/","1":"Other","2":true,"3":true}  
event loadStarted: {}                                                                 
event navigationRequested: {"0":"https://twitter.com/","1":"Other","2":true,"3":true} 
event loadFinished: {"0":"fail"}                                                      
event initialized: {}                                                                 
event navigationRequested: {"0":"http://httpbin.org/","1":"Other","2":true,"3":true}  
event loadStarted: {}                                                                 
event urlChanged: {"0":"http://httpbin.org/"}                                         
event initialized: {}                                                                 
event loadFinished: {"0":"success"}                                                   
title: "httpbin(1): HTTP Client Testing Service"                                      
event navigationRequested: {"0":"http://gfycat.com/","1":"Other","2":true,"3":true}   
event loadStarted: {}                                                                 
event urlChanged: {"0":"http://gfycat.com/"}                                          
event initialized: {}                                                                 
event loadFinished: {"0":"success"}                                                   
err: null                                                                             

How can i click element from querySelector

For example:

document.querySelectorAll('A[href="www.example.com"]').parentNode.querySelector('A.my-link').click();

in evaluate i can access to 'A.my-link' element, but .click() not work.
nightmare.click() - use selector, and not support ...parentNode.querySelector()

add pluggability via .use()

Here's an example of how we can abstract out Swiftly methods:

new Nightmare()
  .use(Swiftly.login(email, password))
  .use(Swiftly.task(description, paths))
  .done(function(err, nightmare){
    if (err) return fn(err);
    fn();
  });
exports.login = function(email, password){
  return function(nightmare) {
    nightmare
      .goto('https://swiftly.com/login')
        .type('#username', email)
        .type('#password', password)
        .click('.button--primary')
      .wait();
  };
}

exports.task = function(description, paths){
  return function(nightmare){
    nightmare
      .goto('https://swiftly.com/create')
      .wait(2000)
      .type('#body', description);

    paths.forEach(function (path) {
      nightmare.upload('input[name=qqfile]', path);
    });

    nightmare
      .wait(5000)
      .click('#task-pay-button')
      .wait(500)
      .click('#pay-button')
      .wait();
    }
  };
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.