├── test ├── mocha.opts ├── files │ ├── upload.txt │ └── upload-again.txt ├── fixtures │ └── upload │ │ └── index.html ├── server.js └── index.js ├── .gitignore ├── package.json ├── README.md └── nightmare-upload.js /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --slow 3s 2 | --timeout 10s 3 | -------------------------------------------------------------------------------- /test/files/upload.txt: -------------------------------------------------------------------------------- 1 | this is an upload test! 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | test/tmp 4 | -------------------------------------------------------------------------------- /test/files/upload-again.txt: -------------------------------------------------------------------------------- 1 | this is an upload test! again! 2 | -------------------------------------------------------------------------------- /test/fixtures/upload/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Uploade 5 | 6 | 7 |
8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /test/server.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var multer = require('multer'); 3 | var path = require('path'); 4 | var serve = require('serve-static'); 5 | var debug= require('debug')('nightmare:server'); 6 | 7 | var app = module.exports = express(); 8 | 9 | app.use(multer({ inMemory: true }).any()); 10 | 11 | app.post('/upload', function (req, res) { 12 | debug('uploading', req.files); 13 | res.send(req.files); 14 | }); 15 | 16 | app.use(serve(path.resolve(__dirname, 'fixtures'))); 17 | 18 | if (!module.parent) app.listen(7500); 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nightmare-upload", 3 | "version": "0.1.1", 4 | "license": "MIT", 5 | "main": "./nightmare-upload.js", 6 | "description":"upload files using NightmareJS", 7 | "scripts": { 8 | "test": "mocha test" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/rosshinkley/nightmare-upload.git" 13 | }, 14 | "author": "Ross Hinkley", 15 | "keywords": [ 16 | "nightmare", 17 | "upload" 18 | ], 19 | "peerDependencies":{ 20 | "nightmare": "^2.3.0" 21 | }, 22 | "dependencies": { 23 | "debug": "^2.2.0" 24 | }, 25 | "devDependencies": { 26 | "rimraf": "^2.4.3", 27 | "mkdirp": "^0.5.1", 28 | "basic-auth": "^1.0.3", 29 | "basic-auth-connect": "^1.0.0", 30 | "chai": "^3.4.1", 31 | "express": "^4.13.3", 32 | "mocha-generators": "^1.2.0", 33 | "mocha": "^2.3.0", 34 | "multer": "1.1.0", 35 | "serve-static": "^1.10.0" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nightmare-upload 2 | Grants the ability to add files to file inputs for Nightmare 2.x, just like the good ol' days of Nightmare 1.x. 3 | 4 | ## Usage 5 | Require the library, passing Nightmare as a reference to add the plugin actions: 6 | 7 | ```js 8 | var Nightmare = require('nightmare'); 9 | require('nightmare-upload')(Nightmare); 10 | ``` 11 | ### .upload(selector, files) 12 | Specify the `files` to add to a `selector` file input. The `files` parameter can be a single string (for a single file) or an array of strings (for multiple files). 13 | 14 | ## Important note about setting file upload inputs 15 | This plugin will not work if the Chromium devtools panel is open as Chromium allows only one attachment to the debugger at a time. 16 | 17 | ## Example 18 | 19 | ```js 20 | var Nightmare = require('nightmare'); 21 | require('nightmare-upload')(Nightmare); 22 | var nightmare = Nightmare(); 23 | nightmare 24 | .goto('http://some-url.tld') 25 | .upload('#some_file_input', '/path/to/my/upload.ext') 26 | .click('#button_that_submits_form_for_upload') 27 | ... 28 | ``` 29 | 30 | ## References/Credits 31 | 32 | * @Zn4rk for [bringing this method to my attention](https://github.com/segmentio/nightmare/issues/235#issuecomment-214226205) 33 | * @svbatalov for the [original implementation](https://github.com/electron/electron/issues/749#issuecomment-213822739) 34 | -------------------------------------------------------------------------------- /nightmare-upload.js: -------------------------------------------------------------------------------- 1 | var debug = require('debug')('nightmare:upload'); 2 | 3 | module.exports = exports = function(Nightmare) { 4 | Nightmare.action('upload', 5 | function(ns, options, parent, win, renderer, done) { 6 | parent.respondTo('upload', function(selector, pathsToUpload, done) { 7 | parent.emit('log', 'paths', pathsToUpload); 8 | try { 9 | //attach the debugger 10 | //NOTE: this will fail if devtools is open 11 | win.webContents.debugger.attach('1.1'); 12 | } catch (e) { 13 | parent.emit('log', 'problem attaching', e); 14 | return done(e); 15 | } 16 | 17 | win.webContents.debugger.sendCommand('DOM.getDocument', {}, function(err, domDocument) { 18 | win.webContents.debugger.sendCommand('DOM.querySelector', { 19 | nodeId: domDocument.root.nodeId, 20 | selector: selector 21 | }, function(err, queryResult) { 22 | //HACK: chromium errors appear to be unpopulated objects? 23 | if (Object.keys(err) 24 | .length > 0) { 25 | parent.emit('log', 'problem selecting', err); 26 | return done(err); 27 | } 28 | win.webContents.debugger.sendCommand('DOM.setFileInputFiles', { 29 | nodeId: queryResult.nodeId, 30 | files: pathsToUpload 31 | }, function(err, setFileResult) { 32 | if (Object.keys(err) 33 | .length > 0) { 34 | parent.emit('log', 'problem setting input', err); 35 | return done(err); 36 | } 37 | win.webContents.debugger.detach(); 38 | done(null, pathsToUpload); 39 | }); 40 | }); 41 | }); 42 | }); 43 | done(); 44 | }, 45 | function(selector, pathsToUpload, done) { 46 | if(!Array.isArray(pathsToUpload)){ 47 | pathsToUpload = [pathsToUpload]; 48 | } 49 | this.child.call('upload', selector, pathsToUpload, (err, stuff) => { 50 | done(err, stuff); 51 | }); 52 | }) 53 | } 54 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies. 3 | */ 4 | 5 | require('mocha-generators') 6 | .install(); 7 | 8 | var Nightmare = require('nightmare'); 9 | var should = require('chai') 10 | .should(); 11 | var url = require('url'); 12 | var server = require('./server'); 13 | var fs = require('fs'); 14 | var mkdirp = require('mkdirp'); 15 | var path = require('path'); 16 | var rimraf = require('rimraf'); 17 | 18 | /** 19 | * Temporary directory 20 | */ 21 | 22 | var tmp_dir = path.join(__dirname, 'tmp') 23 | 24 | /** 25 | * Get rid of a warning. 26 | */ 27 | 28 | process.setMaxListeners(0); 29 | 30 | /** 31 | * Locals. 32 | */ 33 | 34 | var base = 'http://localhost:7500/'; 35 | 36 | describe('Nightmare Upload', function() { 37 | before(function(done) { 38 | require('../nightmare-upload')(Nightmare); 39 | server.listen(7500, done); 40 | }); 41 | 42 | it('should be constructable', function * () { 43 | var nightmare = Nightmare(); 44 | nightmare.should.be.ok; 45 | yield nightmare.end(); 46 | }); 47 | 48 | describe('upload', function() { 49 | it('should upload a single file', function * () { 50 | var nightmare = new Nightmare(); 51 | var files = yield nightmare.goto(fixture('upload')) 52 | .upload('input[type=file]', path.resolve(__dirname, 'files', 'upload.txt')) 53 | .click('button[type=submit]') 54 | .wait(1000) 55 | .evaluate(function() { 56 | return JSON.parse(document.body.querySelector('pre') 57 | .innerHTML) 58 | }); 59 | files[0].originalname.should.equal('upload.txt'); 60 | yield nightmare.end(); 61 | }); 62 | 63 | it('should upload more than one file', function * () { 64 | var nightmare = new Nightmare(); 65 | var files = yield nightmare.goto(fixture('upload')) 66 | .upload('input[type=file]', [ 67 | path.resolve(__dirname, 'files', 'upload.txt'), 68 | path.resolve(__dirname, 'files', 'upload-again.txt') 69 | ]) 70 | .click('button[type=submit]') 71 | .wait(1000) 72 | .evaluate(function() { 73 | return JSON.parse(document.body.querySelector('pre') 74 | .innerHTML) 75 | }); 76 | files.length.should.equal(2); 77 | files[0].originalname.should.equal('upload.txt'); 78 | files[1].originalname.should.equal('upload-again.txt'); 79 | yield nightmare.end(); 80 | }); 81 | 82 | it('should verify a file exists before upload', function(done) { 83 | new Nightmare() 84 | .goto(fixture('upload')) 85 | .upload('#uploaded_file', 'nope.jpg') 86 | .run(function(err) { 87 | err.should.exist; 88 | done(); 89 | }); 90 | }); 91 | }); 92 | }); 93 | 94 | /** 95 | * Generate a URL to a specific fixture. 96 | * @param {String} path 97 | * @returns {String} 98 | */ 99 | 100 | function fixture(path) { 101 | return url.resolve(base, path); 102 | } 103 | --------------------------------------------------------------------------------