nightmare
Version:
A high-level browser automation library.
1,724 lines (1,497 loc) • 77.8 kB
JavaScript
/* global it, describe, before, beforeEach, after, afterEach */
/**
* Module dependencies.
*/
require('mocha-generators').install()
var Nightmare = require('..')
var IPC = require('../lib/ipc')
var chai = require('chai')
var url = require('url')
var server = require('./server')
var https = require('https')
var fs = require('fs')
var mkdirp = require('mkdirp')
var path = require('path')
var rimraf = require('rimraf')
var child_process = require('child_process')
var PNG = require('pngjs').PNG
var should = chai.should()
var split = require('split')
var asPromised = require('chai-as-promised')
var assert = require('assert')
chai.use(asPromised)
/**
* Temporary directory
*/
var tmp_dir = path.join(__dirname, 'tmp')
/**
* Get rid of a warning.
*/
process.setMaxListeners(0)
/**
* Locals.
*/
var base = 'http://localhost:7500/'
describe('Nightmare', function() {
before(function(done) {
server.listen(7500, done)
Nightmare = withDeprecationTracking(Nightmare)
})
after(function() {
Nightmare.assertNoDeprecations()
})
it('should be constructable', function*() {
var nightmare = Nightmare()
nightmare.should.be.ok
yield nightmare.end()
})
it('should have version information', function*() {
var nightmare = Nightmare()
var versions = yield nightmare.engineVersions()
nightmare.engineVersions.electron.should.be.ok
nightmare.engineVersions.chrome.should.be.ok
versions.electron.should.be.ok
versions.chrome.should.be.ok
Nightmare.version.should.be.ok
yield nightmare.end()
})
it('should kill its electron process when it is killed', function(done) {
var child = child_process.fork(
path.join(__dirname, 'files', 'nightmare-unended.js')
)
child.once('message', function(electronPid) {
child.once('exit', function() {
try {
electronPid.should.not.be.a.process
} catch (error) {
// if the test failed, clean up the still-running process
process.kill(electronPid, 'SIGINT')
throw error
}
done()
})
child.kill()
})
})
it('should gracefully handle electron being killed', function(done) {
var child = child_process.fork(
path.join(__dirname, 'files', 'nightmare-unended.js')
)
child.once('message', function(electronPid) {
process.kill(electronPid, 'SIGINT')
child.once('exit', function() {
electronPid.should.not.be.a.process
done()
})
})
})
it('should end gracefully if the chain has not been started', function(done) {
var child = child_process.fork(
path.join(__dirname, 'files', 'nightmare-created.js')
)
child.once('message', function() {
child.once('exit', function(code) {
code.should.equal(0)
done()
})
child.kill()
})
})
it('should exit with a non-zero code on uncaughtExecption', function(done) {
var child = child_process.fork(
path.join(__dirname, 'files', 'nightmare-error.js'),
[],
{ silent: true }
)
child.once('exit', function(code) {
code.should.not.equal(0)
done()
})
})
it('should provide a .catch function', function(done) {
var nightmare = Nightmare()
nightmare
.goto('about:blank')
.evaluate(function() {
throw new Error('Test')
})
.catch(function(err) {
assert.equal(err instanceof Error, true)
assert.equal(err.message, 'Test')
done()
})
})
it('should allow ending more than once', function(done) {
var nightmare = Nightmare()
nightmare
.goto(fixture('navigation'))
.end()
.then(() => nightmare.end())
.then(() => done())
})
it('should allow end with a callback', function(done) {
var nightmare = Nightmare()
nightmare.goto(fixture('navigation')).end(() => done())
})
it('should allow end with a callback to be thenable', function(done) {
var nightmare = Nightmare()
nightmare
.goto(fixture('navigation'))
.end(() => 'nightmare')
.then(str => {
str.should.equal('nightmare')
done()
})
})
it('should kill electron process when halted', function() {
var nightmare = Nightmare()
const check1 = nightmare
.goto(fixture('navigation'))
.wait(1000)
.wait(500)
.end()
.then(() => {})
.should.be.rejectedWith('Nightmare Halted')
const electronPid = nightmare.proc.pid
const check2 = new Promise((resolve, _reject) => {
nightmare.halt('Nightmare Halted', () => {
electronPid.should.not.be.a.process
resolve()
})
})
return Promise.all([check1, check2])
})
it('should successfully end on pages setting onunload or onbeforeunload', function(done) {
var nightmare = Nightmare()
nightmare
.goto(fixture('unload'))
.end()
.then(() => done())
})
it('should successfully end on pages binding unload or beforeunload', function(done) {
var nightmare = Nightmare()
nightmare
.goto(fixture('unload/add-event-listener.html'))
.end()
.then(() => done())
})
it('should provide useful errors for .click', function(done) {
var nightmare = Nightmare()
nightmare
.goto('about:blank')
.click('a.not-here')
.catch(function(err) {
assert.equal(err instanceof Error, true)
err.message.should.include('a.not-here')
done()
})
})
it('should provide useful errors for .mousedown', function(done) {
var nightmare = Nightmare()
nightmare
.goto('about:blank')
.mousedown('a.not-here')
.catch(function(err) {
assert.equal(err instanceof Error, true)
err.message.should.include('a.not-here')
done()
})
})
it('should provide useful errors for .mouseup', function(done) {
var nightmare = Nightmare()
nightmare
.goto('about:blank')
.mouseup('a.not-here')
.catch(function(err) {
assert.equal(err instanceof Error, true)
err.message.should.include('a.not-here')
done()
})
})
it('should provide useful errors for .mouseover', function(done) {
var nightmare = Nightmare()
nightmare
.goto('about:blank')
.mouseover('a.not-here')
.catch(function(err) {
assert.equal(err instanceof Error, true)
err.message.should.include('a.not-here')
done()
})
})
describe('navigation', function() {
var nightmare
beforeEach(function() {
nightmare = Nightmare({
webPreferences: { partition: 'test-partition' + Math.random() },
loadTimeout: 45 * 1000,
waitTimeout: 5 * 1000
})
})
afterEach(function*() {
yield nightmare.end()
Nightmare.resetActions()
})
it('should return data about the response', function*() {
var data = yield nightmare.goto(fixture('navigation'))
data.should.contain.keys('url', 'code', 'method', 'referrer', 'headers')
})
it('should reject with a useful message when no URL', function() {
return nightmare.goto(undefined).then(
function() {
throw new Error('goto(undefined) didn’t cause an error')
},
function(err) {
assert.equal(err instanceof Error, true)
err.message.should.include('url')
}
)
})
it('should reject with a useful message for an empty URL', function() {
return nightmare.goto('').then(
function() {
throw new Error('goto(undefined) didn’t cause an error')
},
function(err) {
assert.equal(err instanceof Error, true)
err.message.should.include('url')
}
)
})
it('should click on a link and then go back', function*() {
var title = yield nightmare
.goto(fixture('navigation'))
.click('a')
.title()
title.should.equal('A')
title = yield nightmare.back().title()
title.should.equal('Navigation')
})
it('should work for links that dont go anywhere', function*() {
var title = yield nightmare
.goto(fixture('navigation'))
.click('a')
.title()
title.should.equal('A')
title = yield nightmare.click('.d').title()
title.should.equal('A')
})
it('should click on a link, go back, and then go forward', function*() {
yield nightmare
.goto(fixture('navigation'))
.click('a')
.back()
.forward()
})
it('should refresh the page', function*() {
yield nightmare.goto(fixture('navigation')).refresh()
})
it('should wait until element is present', function*() {
yield nightmare.goto(fixture('navigation')).wait('a')
})
it('should soft timeout if element does not appear', function*() {
yield nightmare.goto(fixture('navigation')).wait('ul', 150)
})
it('should wait until element is present with a modified poll interval', function*() {
nightmare = Nightmare({
pollInterval: 50
})
yield nightmare.goto(fixture('navigation')).wait('a')
})
it('should escape the css selector correctly when waiting for an element', function*() {
yield nightmare.goto(fixture('navigation')).wait('#escaping\\:test')
})
it('should wait until the evaluate fn returns true', function*() {
yield nightmare.goto(fixture('navigation')).wait(function() {
var text = document.querySelector('a').textContent
return text === 'A'
})
})
it('should wait until the evaluate fn with arguments returns true', function*() {
yield nightmare.goto(fixture('navigation')).wait(
function(expectedA, expectedB) {
var textA = document.querySelector('a.a').textContent
var textB = document.querySelector('a.b').textContent
return expectedA === textA && expectedB === textB
},
'A',
'B'
)
})
describe('asynchronous wait', function() {
it('should wait until the evaluate fn with arguments returns true with a callback', function*() {
yield nightmare.goto(fixture('navigation')).wait(
function(expectedA, expectedB, done) {
setTimeout(() => {
var textA = document.querySelector('a.a').textContent
var textB = document.querySelector('a.b').textContent
done(null, expectedA === textA && expectedB === textB)
}, 2000)
},
'A',
'B'
)
})
it('should wait until the evaluate fn with arguments returns true with a promise', function*() {
yield nightmare.goto(fixture('navigation')).wait(
function(expectedA, expectedB) {
return new Promise(function(resolve) {
setTimeout(() => {
var textA = document.querySelector('a.a').textContent
var textB = document.querySelector('a.b').textContent
resolve(expectedA === textA && expectedB === textB)
}, 2000)
})
},
'A',
'B'
)
})
it('should reject timeout on wait', function*() {
yield nightmare.goto(fixture('navigation')).wait(function(_done) {
//never call done
}).should.be.rejected
})
it('should reject timeout on wait with selector', function*() {
yield nightmare
.goto(fixture('navigation'))
.wait('#non-existent')
.should.be.rejected.then(function(error) {
error.message.should.include('#non-existent')
})
})
it('should run multiple times before timeout on wait', function*() {
yield nightmare.goto(fixture('navigation')).wait(function(done) {
setTimeout(() => done(null, false), 500)
}).should.be.rejected
})
})
it('should fail if navigation target is invalid', function() {
return nightmare.goto('http://this-is-not-a-real-domain.tld').then(
function() {
throw new Error('Navigation to an invalid domain succeeded')
},
function(err) {
assert.equal(err instanceof Error, true)
assert.equal(err.message, 'navigation error')
assert.equal(err.details, 'ERR_NAME_NOT_RESOLVED')
assert.equal(err.code, -105)
assert.equal(err.url, 'http://this-is-not-a-real-domain.tld/')
}
)
})
it('should fail if navigation target is a malformed URL', function(done) {
nightmare
.goto('somewhere out there')
.then(function() {
done(new Error('Navigation to an invalid domain succeeded'))
})
.catch(function(_error) {
done()
})
})
it('should fail if navigating to an unknown protocol', function(done) {
nightmare
.goto('fake-protocol://blahblahblah')
.then(function() {
done(new Error('Navigation to an invalid protocol succeeded'))
})
.catch(function(_error) {
done()
})
})
it('should not fail if the URL loads but a resource fails', function() {
return nightmare.goto(fixture('navigation/invalid-image'))
})
it('should not fail if a child frame fails', function() {
return nightmare.goto(fixture('navigation/invalid-frame'))
})
it('should return correct data when child frames are present', function*() {
var data = yield nightmare.goto(fixture('navigation/valid-frame'))
data.should.have.property('url')
data.url.should.equal(fixture('navigation/valid-frame'))
})
it('should not fail if response was a valid error (e.g. 404)', function() {
return nightmare.goto(fixture('navigation/not-a-real-page'))
})
it('should fail if the response dies in flight', function(done) {
nightmare
.goto(fixture('do-not-respond'))
.then(function() {
done(new Error('Navigation succeeded but server connection died'))
})
.catch(function(_error) {
done()
})
})
it('should not fail for a redirect', function() {
return nightmare.goto(fixture('redirect?url=%2Fnavigation'))
})
it('should fail for a redirect to an invalid URL', function(done) {
nightmare
.goto(
fixture('redirect?url=http%3A%2F%2Fthis-is-not-a-real-domain.tld')
)
.then(function() {
done(new Error('Navigation succeeded with redirect to bad location'))
})
.catch(function(_error) {
done()
})
})
it('should succeed properly if request handler is present', function() {
Nightmare.action(
'monitorRequest',
function(name, options, parent, win, renderer, done) {
win.webContents.session.webRequest.onBeforeRequest(
['*://localhost:*'],
function(details, callback) {
callback({ cancel: false })
}
)
done()
},
function(done) {
done()
return this
}
)
return Nightmare({ webPreferences: { partition: 'test-partition' } })
.goto(fixture('navigation'))
.end()
})
it('should fail properly if request handler is present', function(done) {
Nightmare.action(
'monitorRequest',
function(name, options, parent, win, renderer, done) {
win.webContents.session.webRequest.onBeforeRequest(
['*://localhost:*'],
function(details, callback) {
callback({ cancel: false })
}
)
done()
},
function(done) {
done()
return this
}
)
Nightmare({ webPreferences: { partition: 'test-partition' } })
.goto('http://this-is-not-a-real-domain.tld')
.then(function() {
done(new Error('Navigation to an invalid domain succeeded'))
})
.catch(function(_error) {
done()
})
})
it('should support javascript URLs', function*() {
var gotoResult = yield nightmare
.goto(fixture('navigation'))
.goto(
'javascript:void(document.querySelector(".a").textContent="LINK");'
)
gotoResult.should.be.an('object')
var linkText = yield nightmare.evaluate(function() {
return document.querySelector('.a').textContent
})
linkText.should.equal('LINK')
})
it('should support javascript URLs that load pages', function*() {
var data = yield nightmare
.goto(fixture('navigation'))
.goto(`javascript:window.location='${fixture('navigation/a.html')}'`)
data.should.contain.keys('url', 'code', 'method', 'referrer', 'headers')
data.url.should.equal(fixture('navigation/a.html'))
var linkText = yield nightmare.evaluate(function() {
return document.querySelector('.d').textContent
})
linkText.should.equal('D')
})
it('should fail immediately/not time out for 304 statuses', function() {
return Nightmare({ gotoTimeout: 500 })
.goto(fixture('not-modified'))
.end()
.then(
function() {
throw new Error('Navigating to a 304 should return an error')
},
function(error) {
if (error.code === -7) {
throw new Error('Navigating to a 304 should not time out')
}
}
)
})
it('should not time out for aborted loads', function() {
Nightmare.action(
'abortRequests',
function(name, options, parent, win, renderer, done) {
win.webContents.session.webRequest.onBeforeRequest(
['*://localhost:*'],
function(details, callback) {
setTimeout(() => win.webContents.stop(), 0)
callback({ cancel: false })
}
)
done()
},
function() {}
)
return Nightmare({ gotoTimeout: 500 })
.goto(fixture('navigation'))
.end()
.then(
function() {
throw new Error('An aborted page load should return an error')
},
function(error) {
if (error.code === -7) {
throw new Error('Aborting a page load should not time out')
}
}
)
})
describe('timeouts', function() {
it('should time out after 30 seconds of loading', function() {
// allow this test to go particularly long
this.timeout(40000)
return nightmare
.goto(fixture('wait'))
.should.be.rejected.then(function(error) {
error.code.should.equal(-7)
})
})
it('should allow custom goto timeout on the constructor', function() {
var startTime = Date.now()
return Nightmare({ gotoTimeout: 1000 })
.goto(fixture('wait'))
.end()
.should.be.rejected.then(function(_error) {
// allow a few extra seconds for browser startup
;(startTime - Date.now()).should.be.below(3000)
})
})
it('should allow a timeout to succeed if DOM loaded', function() {
return Nightmare({ gotoTimeout: 1000 })
.goto(fixture('navigation/hanging-resources.html'))
.end()
.then(function(data) {
data.details.should.include('1000 ms')
})
})
it('should allow actions on a hanging page', function() {
return Nightmare({ gotoTimeout: 500 })
.goto(fixture('navigation/hanging-resources.html'))
.evaluate(() => document.title)
.end()
.then(function(title) {
title.should.equal('Hanging resource load')
})
})
it('should allow loading a new page after timing out', function() {
nightmare.end().then()
nightmare = Nightmare({ gotoTimeout: 1000 })
return nightmare
.goto(fixture('wait'))
.should.be.rejected.then(function() {
return nightmare.goto(fixture('navigation'))
})
})
it('should allow for timeouts for non-goto loads', function*() {
// ###
this.timeout(40000)
var nightmare = Nightmare({ loadTimeout: 30000 })
yield nightmare.goto(fixture('navigation')).click('#never-ends')
yield nightmare.end()
})
})
})
describe('evaluation', function() {
var nightmare
beforeEach(function() {
nightmare = Nightmare()
})
afterEach(function*() {
yield nightmare.end()
})
it('should get the title', function*() {
var title = yield nightmare.goto(fixture('evaluation')).title()
title.should.eql('Evaluation')
})
it('should get the url', function*() {
var url = yield nightmare.goto(fixture('evaluation')).url()
url.should.have.string(fixture('evaluation'))
})
it('should get the path', function*() {
var path = yield nightmare.goto(fixture('evaluation')).path()
var formalUrl = fixture('evaluation') + '/'
formalUrl.should.have.string(path)
})
it('should check if the selector exists', function*() {
// existent element
var exists = yield nightmare
.goto(fixture('evaluation'))
.exists('h1.title')
exists.should.be.true
// non-existent element
exists = yield nightmare.exists('a.blahblahblah')
exists.should.be.false
})
it('should check if an element is visible', function*() {
// visible element
var visible = yield nightmare
.goto(fixture('evaluation'))
.visible('h1.title')
visible.should.be.true
// hidden element
visible = yield nightmare.visible('.hidden')
visible.should.be.false
// non-existent element
visible = yield nightmare.visible('#asdfasdfasdf')
visible.should.be.false
})
it('should evaluate javascript on the page, with parameters', function*() {
var title = yield nightmare
.goto(fixture('evaluation'))
.evaluate(function(parameter) {
return document.title + ' -- ' + parameter
}, 'testparameter')
title.should.equal('Evaluation -- testparameter')
})
it('should capture invalid evaluate fn', function() {
return nightmare.goto(fixture('evaluation')).evaluate('not_a_function')
.should.be.rejected
})
describe('asynchronous', function() {
it('should allow for asynchronous evaluation with a callback', function*() {
var asyncValue = yield nightmare
.goto(fixture('evaluation'))
.evaluate(function(done) {
setTimeout(() => done(null, 'nightmare'), 1000)
})
asyncValue.should.equal('nightmare')
})
it('should allow for arguments with asynchronous evaluation with a callback', function*() {
var asyncValue = yield nightmare
.goto(fixture('evaluation'))
.evaluate(function(str, done) {
setTimeout(() => done(null, str), 1000)
}, 'nightmare')
asyncValue.should.equal('nightmare')
})
it('should allow for errors in asynchronous evaluation with a callback', function*() {
yield nightmare.goto(fixture('evaluation')).evaluate(function(done) {
setTimeout(() => done(new Error('nightmare')), 1000)
}).should.be.rejected
})
it('should allow for timeouts in asynchronous evaluation with a callback', function*() {
this.timeout(40000)
yield nightmare.goto(fixture('evaluation')).evaluate(function(_done) {
//don't call done
}).should.be.rejected
})
it('should allow for asynchronous evaluation with a promise', function*() {
var asyncValue = yield nightmare
.goto(fixture('evaluation'))
.evaluate(function() {
return new Promise(resolve => {
setTimeout(() => resolve('nightmare'), 1000)
})
})
asyncValue.should.equal('nightmare')
})
it('should allow for arguments with asynchronous evaluation with a promise', function*() {
var asyncValue = yield nightmare
.goto(fixture('evaluation'))
.evaluate(function(str) {
return new Promise(resolve => {
setTimeout(() => resolve(str), 1000)
})
}, 'nightmare')
asyncValue.should.equal('nightmare')
})
it('should allow for errors in asynchronous evaluation with a promise', function*() {
yield nightmare.goto(fixture('evaluation')).evaluate(function() {
return new Promise((resolve, reject) => {
setTimeout(() => reject(new Error('nightmare')), 1000)
})
}).should.be.rejected
})
it('should allow for timeouts in asynchronous evaluation with a promise', function*() {
this.timeout(40000)
yield nightmare.goto(fixture('evaluation')).evaluate(function() {
return new Promise((_resolve, _reject) => {
return 'nightmare'
})
}).should.be.rejected
})
})
})
describe('manipulation', function() {
var nightmare
beforeEach(function() {
nightmare = Nightmare()
})
afterEach(function*() {
yield nightmare.end()
})
it('should inject javascript onto the page', function*() {
var globalNumber = yield nightmare
.goto(fixture('manipulation'))
.inject('js', 'test/files/globals.js')
.evaluate(function() {
return globalNumber
})
globalNumber.should.equal(7)
var numAnchors = yield nightmare
.goto(fixture('manipulation'))
.inject('js', 'test/files/jquery-2.1.1.min.js')
.evaluate(function() {
return window.$('h1').length
})
numAnchors.should.equal(1)
})
it('should inject javascript onto the page ending with a comment', function*() {
var globalNumber = yield nightmare
.goto(fixture('manipulation'))
.inject('js', 'test/files/globals.js')
.evaluate(function() {
return globalNumber
})
globalNumber.should.equal(7)
var numAnchors = yield nightmare
.goto(fixture('manipulation'))
.inject('js', 'test/files/jquery-1.9.0.min.js')
.evaluate(function() {
return window.$('h1').length
})
numAnchors.should.equal(1)
})
it('should inject css onto the page', function*() {
var color = yield nightmare
.goto(fixture('manipulation'))
.inject('js', 'test/files/jquery-2.1.1.min.js')
.inject('css', 'test/files/test.css')
.evaluate(function() {
return window.$('body').css('background-color')
})
color.should.equal('rgb(255, 0, 0)')
})
it('should not inject unsupported types onto the page', function*() {
var color = yield nightmare
.goto(fixture('manipulation'))
.inject('js', 'test/files/jquery-2.1.1.min.js')
.inject('pdf', 'test/files/test.css')
.evaluate(function() {
return window.$('body').css('background-color')
})
color.should.not.equal('rgb(255, 0, 0)')
})
it('should type', function*() {
var input = 'nightmare'
var events = input.length * 3
var value = yield nightmare
.on('console', function(type, _input, _message) {
if (type === 'log') events--
})
.goto(fixture('manipulation'))
.type('input[type=search]', input)
.evaluate(function() {
return document.querySelector('input[type=search]').value
})
value.should.equal('nightmare')
events.should.equal(0)
})
it('should type integer', function*() {
var input = 10
var events = input.toString().length * 3
var value = yield nightmare
.on('console', function(type, _input, _message) {
if (type === 'log') events--
})
.goto(fixture('manipulation'))
.type('input[type=search]', input)
.evaluate(function() {
return document.querySelector('input[type=search]').value
})
value.should.equal('10')
events.should.equal(0)
})
it('should type object', function*() {
var input = {
foo: 'bar'
}
var events = input.toString().length * 3
var value = yield nightmare
.on('console', function(type, _input, _message) {
if (type === 'log') events--
})
.goto(fixture('manipulation'))
.type('input[type=search]', input)
.evaluate(function() {
return document.querySelector('input[type=search]').value
})
value.should.equal('[object Object]')
events.should.equal(0)
})
it('should clear inputs', function*() {
var input = 'nightmare'
var events = input.length * 3
var value = yield nightmare
.on('console', function(type, _input, _message) {
if (type === 'log') events--
})
.goto(fixture('manipulation'))
.type('input[type=search]', input)
.type('input[type=search]')
.evaluate(function() {
return document.querySelector('input[type=search]').value
})
value.should.equal('')
events.should.equal(0)
})
it('should support inserting text', function*() {
var input = 'nightmare insert typing'
var value = yield nightmare
.goto(fixture('manipulation'))
.insert('input[type=search]', input)
.evaluate(function() {
return document.querySelector('input[type=search]').value
})
value.should.equal('nightmare insert typing')
})
it('should support clearing inserted text', function*() {
var value = yield nightmare
.goto(fixture('manipulation'))
.insert('input[type=search]')
.evaluate(function() {
return document.querySelector('input[type=search]').value
})
value.should.equal('')
})
it('should not type in a nonexistent selector', function() {
return nightmare
.goto(fixture('manipulation'))
.type('does-not-exist', 'nightmare').should.be.rejected
})
it('should not insert in a nonexistent selector', function() {
return nightmare
.goto(fixture('manipulation'))
.insert('does-not-exist', 'nightmare').should.be.rejected
})
it('should blur the active element when something is clicked', function*() {
var isBody = yield nightmare
.goto(fixture('manipulation'))
.type('input[type=search]', 'test')
.click('p')
.evaluate(function() {
return document.activeElement === document.body
})
isBody.should.be.true
})
it('should allow for clicking on elements with attribute selectors', function*() {
yield nightmare
.goto(fixture('manipulation'))
.click('div[data-test="test"]')
})
it('should not allow for code injection with .click()', function(done) {
var exception
nightmare
.goto(fixture('manipulation'))
.click("\"]'); document.title = 'injected title'; ('\"")
.catch(e => (exception = e))
.then(() => nightmare.title())
.then(title => {
exception.should.exist
title.should.equal('Manipulation')
done()
})
})
it('should not fail if selector no longer exists to blur after typing', function*() {
yield nightmare
.on('console', function() {
// console.log(arguments)
})
.goto(fixture('manipulation'))
.type('input#disappears', 'nightmare')
})
it('should type and click', function*() {
var title = yield nightmare
.goto(fixture('manipulation'))
.type('input[type=search]', 'nightmare')
.click('button[type=submit]')
.wait(500)
.title()
title.should.equal('Manipulation - Results')
})
it('should type and click several times', function*() {
var title = yield nightmare
.goto(fixture('manipulation'))
.type('input[type=search]', 'github nightmare')
.click('button[type=submit]')
.wait(500)
.click('a')
.wait(500)
.title()
title.should.equal('Manipulation - Result - Nightmare')
})
it('should checkbox', function*() {
var checkbox = yield nightmare
.goto(fixture('manipulation'))
.check('input[type=checkbox]')
.evaluate(function() {
return document.querySelector('input[type=checkbox]').checked
})
checkbox.should.be.true
})
it('should uncheck', function*() {
var checkbox = yield nightmare
.goto(fixture('manipulation'))
.check('input[type=checkbox]')
.uncheck('input[type=checkbox]')
.evaluate(function() {
return document.querySelector('input[type=checkbox]').checked
})
checkbox.should.be.false
})
it('should select', function*() {
var select = yield nightmare
.goto(fixture('manipulation'))
.select('select', 'b')
.evaluate(function() {
return document.querySelector('select').value
})
select.should.equal('b')
})
it('should scroll to specified position', function*() {
// start at the top
var coordinates = yield nightmare
.viewport(320, 320)
.goto(fixture('manipulation'))
.evaluate(function() {
return {
top: document.scrollingElement.scrollTop,
left: document.scrollingElement.scrollLeft
}
})
coordinates.top.should.equal(0)
coordinates.left.should.equal(0)
// scroll down a bit
coordinates = yield nightmare.scrollTo(100, 50).evaluate(function() {
return {
top: document.scrollingElement.scrollTop,
left: document.scrollingElement.scrollLeft
}
})
coordinates.top.should.equal(100)
coordinates.left.should.equal(50)
})
it('should hover over an element', function*() {
var color = yield nightmare
.goto(fixture('manipulation'))
.mouseover('h1')
.evaluate(function() {
var element = document.querySelector('h1')
return element.style.background
})
color.should.equal('rgb(102, 255, 102)')
})
it('should release hover from an element', function*() {
var hoverColor = 'rgb(0, 255, 255)'
var hoveredColor = yield nightmare
.goto(fixture('manipulation'))
.mouseover('h2')
.evaluate(function() {
var element = document.querySelector('h2')
return element.style.background
})
hoveredColor.should.equal(hoverColor)
var nonHoveredColor = yield nightmare.mouseout('h2').evaluate(function() {
var element = document.querySelector('h2')
return element.style.background
})
nonHoveredColor.should.not.equal(hoverColor)
})
it('should mousedown on an element', function*() {
var color = yield nightmare
.goto(fixture('manipulation'))
.mousedown('h1')
.evaluate(function() {
var element = document.querySelector('h1')
return element.style.background
})
color.should.equal('rgb(255, 0, 0)')
})
})
describe('cookies', function() {
var nightmare
beforeEach(function() {
nightmare = Nightmare({
webPreferences: { partition: 'test-partition' }
}).goto(fixture('cookie'))
})
afterEach(function*() {
yield nightmare.end()
})
it('.set(name, value) & .get(name)', function*() {
var cookies = nightmare.cookies
yield cookies.set('hi', 'hello')
var cookie = yield cookies.get('hi')
cookie.name.should.equal('hi')
cookie.value.should.equal('hello')
cookie.path.should.equal('/')
cookie.secure.should.equal(false)
})
it('.set(obj) & .get(name)', function*() {
var cookies = nightmare.cookies
yield cookies.set({
name: 'nightmare',
value: 'rocks',
path: '/cookie'
})
var cookie = yield cookies.get('nightmare')
cookie.name.should.equal('nightmare')
cookie.value.should.equal('rocks')
cookie.path.should.equal('/cookie')
cookie.secure.should.equal(false)
})
it('.set([cookie1, cookie2]) & .get()', function*() {
var cookies = nightmare.cookies
yield cookies.set([
{
name: 'hi',
value: 'hello',
path: '/'
},
{
name: 'nightmare',
value: 'rocks',
path: '/cookie'
}
])
cookies = yield cookies.get()
cookies.length.should.equal(2)
// sort in case they come in a different order
cookies = cookies.sort(function(a, b) {
if (a.name > b.name) return 1
if (a.name < b.name) return -1
return 0
})
cookies[0].name.should.equal('hi')
cookies[0].value.should.equal('hello')
cookies[0].path.should.equal('/')
cookies[0].secure.should.equal(false)
cookies[1].name.should.equal('nightmare')
cookies[1].value.should.equal('rocks')
cookies[1].path.should.equal('/cookie')
cookies[1].secure.should.equal(false)
})
it('.set([cookie1, cookie2]) & .get(query)', function*() {
var cookies = nightmare.cookies
yield cookies.set([
{
name: 'hi',
value: 'hello',
path: '/'
},
{
name: 'nightmare',
value: 'rocks',
path: '/cookie'
}
])
cookies = yield cookies.get({ path: '/cookie' })
cookies.length.should.equal(1)
cookies[0].name.should.equal('nightmare')
cookies[0].value.should.equal('rocks')
cookies[0].path.should.equal('/cookie')
cookies[0].secure.should.equal(false)
})
it('.set([cookie]) & .clear(name) & .get(query)', function*() {
var cookies = nightmare.cookies
yield cookies.set([
{
name: 'hi',
value: 'hello',
path: '/'
},
{
name: 'nightmare',
value: 'rocks',
path: '/cookie'
}
])
yield cookies.clear('nightmare')
cookies = yield cookies.get({ path: '/cookie' })
cookies.length.should.equal(0)
})
it('.set([cookie]) & .clear() & .get()', function*() {
var cookies = nightmare.cookies
yield cookies.set([
{
name: 'hi',
value: 'hello',
path: '/'
},
{
name: 'nightmare',
value: 'rocks',
path: '/cookie'
}
])
yield cookies.clear()
cookies = yield cookies.get()
cookies.length.should.equal(0)
})
it('.set([cookie]) & .clearAll() & .get()', function*() {
yield nightmare.cookies.set([
{
name: 'hi',
value: 'hello',
path: '/'
},
{
name: 'nightmare',
value: 'rocks',
path: '/cookie'
}
])
yield nightmare.goto(fixture('simple'))
yield nightmare.cookies.set([
{
name: 'hi',
value: 'hello',
path: '/'
},
{
name: 'nightmare',
value: 'rocks',
path: '/cookie'
}
])
yield nightmare.cookies.clearAll()
var cookies = yield nightmare.cookies.get()
cookies.length.should.equal(0)
yield nightmare.goto(fixture('cookie'))
cookies = yield nightmare.cookies.get()
cookies.length.should.equal(0)
})
it('should return a proper error', function*() {
try {
yield nightmare.goto(fixture('cookie')).cookies.set({
name: 'hi',
value: 'there',
domain: 'https://www.google.com'
})
assert.fail("shouldn't have got here")
} catch (e) {
assert.equal(e.message, 'Setting cookie failed')
}
})
})
describe('rendering', function() {
this.timeout('30s')
var nightmare
before(function(done) {
mkdirp(tmp_dir, done)
})
after(function(done) {
rimraf(tmp_dir, done)
})
beforeEach(function() {
nightmare = Nightmare()
})
afterEach(function*() {
yield nightmare.end()
})
it('should take a screenshot', function*() {
yield nightmare
.goto('https://github.com/')
.screenshot(tmp_dir + '/test.png')
var stats = fs.statSync(tmp_dir + '/test.png')
stats.size.should.be.at.least(1000)
})
it('should buffer a screenshot', function*() {
var image = yield nightmare.goto('https://github.com').screenshot()
Buffer.isBuffer(image).should.be.true
image.length.should.be.at.least(1000)
})
it('should take a clipped screenshot', function*() {
yield nightmare
.goto('https://github.com/')
.screenshot(tmp_dir + '/test-clipped.png', {
x: 200,
y: 100,
width: 100,
height: 100
})
var stats = fs.statSync(tmp_dir + '/test.png')
var statsClipped = fs.statSync(tmp_dir + '/test-clipped.png')
statsClipped.size.should.be.at.least(300)
stats.size.should.be.at.least(10 * statsClipped.size)
})
it('should buffer a clipped screenshot', function*() {
var image = yield nightmare.goto('https://github.com').screenshot({
x: 200,
y: 100,
width: 100,
height: 100
})
Buffer.isBuffer(image).should.be.true
image.length.should.be.at.least(300)
})
// repeat this test 3 times, since the concern here is non-determinism in
// the timing accuracy of screenshots -- it might pass once, but likely not
// several times in a row.
// Temporarily disabled to allow Circle CI to pass, will revisit in 3.0 release
/*
for (var i = 0; i < 3; i++) {
it('should screenshot an up-to-date image of the page (' + i + ')', function*() {
var image = yield nightmare
.goto('about:blank')
.viewport(100, 100)
.evaluate(function() { document.body.style.background = '#090'; })
.evaluate(function() { document.body.style.background = '#090'; })
.wait(1000)
.screenshot();
var png = new PNG();
var imageData = yield png.parse.bind(png, image);
var firstPixel = Array.from(imageData.data.slice(0, 3));
firstPixel.should.deep.equal([0, 153, 0]);
});
}
*/
it('should screenshot an idle page', function*() {
var image = yield nightmare
.goto('about:blank')
.viewport(100, 100)
.evaluate(function() {
document.body.style.background = '#F0F'
})
.evaluate(function() {
document.body.style.background = '#0F0'
})
.wait(1000)
.screenshot()
var png = new PNG()
var imageData = yield png.parse.bind(png, image)
var firstPixel = Array.from(imageData.data.slice(0, 3))
// Since color profiles can affect the final output image depending
// on platform, the most we can expect is that the G channel is greater
// than the other two.
firstPixel[1].should.be.above(firstPixel[0])
firstPixel[1].should.be.above(firstPixel[2])
})
it('should not subscribe to frames until necessary', function() {
var didSubscribe = false
var FrameManager = require('../lib/frame-manager.js')
FrameManager({
webContents: {
beginFrameSubscription: function() {
didSubscribe = true
},
endFrameSubscription: function() {},
executeJavaScript: function() {}
}
})
didSubscribe.should.be.false
})
it('should subscribe to frames when requested necessary', function(done) {
var didSubscribe = false
var didUnsubscribe = false
var FrameManager = require('../lib/frame-manager.js')
var fn
var manager = FrameManager({
webContents: {
debugger: {
isAttached: function() {
return true
},
sendCommand: function(command) {
if (command === 'DOM.highlightRect') {
fn('mock-data')
}
}
},
beginFrameSubscription: function(_fn) {
didSubscribe = true
fn = _fn
},
endFrameSubscription: function() {
didUnsubscribe = true
},
executeJavaScript: function() {}
}
})
manager.requestFrame(function(data) {
didSubscribe.should.be.true
didUnsubscribe.should.be.true
data.should.equal('mock-data')
done()
})
})
it('should support multiple concurrent frame subscriptions', function(done) {
var subscribeCount = 0
var unsubscribeCount = 0
var FrameManager = require('../lib/frame-manager.js')
var fn = null
var async = require('async')
var manager = FrameManager({
webContents: {
debugger: {
isAttached: function() {
return true
},
sendCommand: function(command) {
if (command === 'DOM.highlightRect') {
setTimeout(function() {
fn('mock-data')
}, 100)
}
}
},
beginFrameSubscription: function(_fn) {
subscribeCount += 1
assert.strictEqual(fn, null)
fn = _fn
},
endFrameSubscription: function() {
unsubscribeCount += 1
fn = null
},
executeJavaScript: function() {}
}
})
async.times(
2,
function requestFrameFn(i, cb) {
manager.requestFrame(function handleFrame(data) {
cb(null, data)
})
},
function handleResults(err, results) {
if (err) {
done(err)
}
subscribeCount.should.equal(1)
unsubscribeCount.should.equal(1)
results[0].should.equal('mock-data')
results[1].should.equal('mock-data')
done()
}
)
})
it('should support multiple series frame subscriptions', function(done) {
var subscribeCount = 0
var unsubscribeCount = 0
var FrameManager = require('../lib/frame-manager.js')
var fn = null
var async = require('async')
var manager = FrameManager({
webContents: {
debugger: {
isAttached: function() {
return true
},
sendCommand: function(command) {
if (command === 'DOM.highlightRect') {
setTimeout(function() {
fn('mock-data')
}, 100)
}
}
},
beginFrameSubscription: function(_fn) {
subscribeCount += 1
assert.strictEqual(fn, null)
fn = _fn
},
endFrameSubscription: function() {
unsubscribeCount += 1
fn = null
},
executeJavaScript: function() {}
}
})
async.timesSeries(
2,
function requestFrameFn(i, cb) {
manager.requestFrame(function handleFrame(data) {
cb(null, data)
})
},
function handleResults(err, results) {
if (err) {
done(err)
}
subscribeCount.should.equal(2)
unsubscribeCount.should.equal(2)
results[0].should.equal('mock-data')
results[1].should.equal('mock-data')
done()
}
)
})
// DEV: We can have multiple timeouts if page is static
it('should support multiple series timing out frame subscriptions', function(done) {
var subscribeCount = 0
var unsubscribeCount = 0
var FrameManager = require('../lib/frame-manager.js')
var fn = null
var async = require('async')
var manager = FrameManager({
webContents: {
debugger: {
isAttached: function() {
return true
},
sendCommand: function() {
/* Ignore command so it times out */
}
},
beginFrameSubscription: function(_fn) {
subscribeCount += 1
assert.strictEqual(fn, null)
fn = _fn
},
endFrameSubscription: function() {
unsubscribeCount += 1
fn = null
},
executeJavaScript: function() {}
}
})
async.timesSeries(
2,
function requestFrameFn(i, cb) {
manager.requestFrame(function handleFrame(data) {
cb(null, data)
}, 100)
},
function handleResults(err, results) {
if (err) {
done(err)
}
subscribeCount.should.equal(2)
unsubscribeCount.should.equal(2)
should.equal(results[0], null)
should.equal(results[1], null)
done()
}
)
})
it('should load jquery correctly', function*() {
var loaded = yield nightmare
.goto(fixture('rendering'))
.wait(2000)
.evaluate(function() {
return !!window.jQuery
})
loaded.should.be.at.least(true)
})
it('should render fonts correctly', function*() {
yield nightmare
.goto(fixture('rendering'))
.wait(200