I want to
save the image file which is in remote web server, and
upload it to another server
in Protractor Test.
// depend on a external module
var fs = require('fs');
// save remote file(url) to local(dest)
var download = function (url, dest) {
// let this function be async
browser.executeAsyncScript(function (url, dest, done) {
var file = fs.createWriteStream(dest);
var request = http.get(url, function (response) {
response.pipe(file);
file.on('finish', function () {
file.close(done);
});
});
}, url, dest);
};
describe('', function () {
it('', function () {
browser.get('http://...');
download('http://.../foo.jpg', 'foo.jpg'); /*** DOESN'T WORK! ***/
var absolutePath = path.resolve(__dirname, 'foo.jpg');
$('input[type=file]').sendKeys(absolutePath);
$('#uploadButton').click();
...
but this doesn't work:
Stacktrace:
UnknownError: javascript error: fs is not defined
When I put var fs = require('fs'); in download function, the error message is below:
Stacktrace:
UnknownError: javascript error: require is not defined
When you call executeAsyncScript the function that you pass is serialized an executed inside the browser. The function will not run in the context of your protractor test, but on the browser.
You need to create a promise that resolves when you are done downloading the file.
// depend on a external module
var fs = require('fs');
describe('', function () {
// save remote file(url) to local(dest)
var download = function (url, dest) {
// Create a promise that will be resolved after download.
var d = protractor.promise.defer();
var file = fs.createWriteStream(dest);
var request = http.get(url, function (response) {
response.pipe(file);
file.on('finish', function () {
file.close();
// The file has been read, resolve the promise
d. fulfill();
});
});
// Return the promise
d.promise;
};
it('', function () {
browser.get('http://...');
// Get the file and wait for the promise to resolve to move on
download('http://.../foo.jpg', 'foo.jpg').then(function() {
// Make sure you specify a path where you can write and read the file.
var absolutePath = path.resolve(__dirname, 'foo.jpg');
$('input[type=file]').sendKeys(absolutePath);
$('#uploadButton').click();
...
});
Let me know if it works
Here is the documentation: https://code.google.com/p/selenium/wiki/WebDriverJs#Deferred_Objects
Related
I'm trying to execute a node script that downloads some urls. It looks like this:
const https = require('https');
const fs = require('fs');
const async = require('async');
function download_cb(err) {
if (err) {
console.log("Download error:"+err);
}
}
function download_file(task) {
const url = task.url;
const dest = task.dest;
var file = fs.createWriteStream(dest);
var request = https.get(url, function(response) {
response.pipe(file);
file.on('finish', function() {
file.close(download_cb); // close() is async, call cb after close completes.
});
}).on('error', function(err) { // Handle errors
fs.unlink(dest); // Delete the file async. (But we don't check the result)
download_cb(err.message);
});
};
function get_urls() {
var queue = async.queue(download_file, 10);
const urls = []; // some array of urls
for (var i=0; i<urls.length; i++) {
queue.push({url: urls[i], dest: dest/*whatever*/});
}
return queue.drain();
}
(async () {
await get_urls().then(()=>{
console.log("All done");
})
})();
This ends up downloading only the first 10 urls and then exits, while the "All done" message is never shown. It somehow seems that the promise returned by the function (queue.drain()) is never resolved even though it's being awaited for. What am I missing?
I also tried:
queue.drain = function() {
console.log("All files are downloaded");
};
inside the get_urls function but it doesn't change anything and the code in it isn't get executed either.
I'm creating a Node.js module with an asynchronous method - a simple HTTP GET request. Here is the code:
//mymodule.js
var https = require('https');
function getSomething(url_str)
{
var callback_fn = function(response){
var body = '';
response.on('data', function (data) {
body += data;
});
response.on('end', function () {
//console.log(body);
return body;
});
};
return https.request(url_str, callback_fn).end();
}
var module_obj = {
getSome: getSomething
};
module.exports = module_obj;
This module is called by my app.js - a web server - like so:
//app.js
var myModule = require('./mymodule');
var http = require('http');
var qs = require('querystring');
var server_fn = function(request, response){
response.setHeader('Access-Control-Allow-Origin', '*');
response.setHeader('Access-Control-Request-Method', '*');
response.setHeader('Access-Control-Allow-Methods', 'OPTIONS, GET');
response.setHeader('Access-Control-Allow-Headers', '*');
if ( request.method === 'OPTIONS' ) {
response.writeHead(200);
response.end();
return;
}
if (request.method == 'POST') {
var body = '';
request.on('data', function (data) {
body += data;
// Too much POST data, kill the connection!
// 1e6 === 1 * Math.pow(10, 6) === 1 * 1000000 ~~~ 1MB
if (body.length > 1e6)
request.connection.destroy();
});
request.on('end', function () {
var post = qs.parse(body),
post_url = post.url,
post_method = post.method;
var promise_flow = new Promise(function(resolve, reject){
if(post_method === 'get_me_something')
{
response_str = myModule.getSome(post_url);
resolve(response_str);
}
else
{
resolve('nothing');
}
});
promise_flow
.then(function(response){
response.write(response);
response.end();
return;
}).catch(function(error){
response.write(error);
response.end();
return;
})
});
}
};
var http_server = http.createServer(server_fn);
http_server.listen(2270);
console.log("server listening on 2270");
So basically, I start things up via node app.js, and then I post the URL, and then the module should fetch the Web page and then return the content.
Unfortunately, I'm getting the following error:
UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): TypeError: First argument must be a string or Buffer
I believe this is because the response I'm getting from my modules getSomething method is false, as opposed to the content of the requested Web page.
I know I can fix this by moving the https.get operation from mymodule.js and putting it inline with app.js, and then calling resolve on end, but I'd like to keep the current module setup.
Is there a workaround to get the asynchronous method in the imported module to work with the existing promise chain as setup?
UPDATE
After further review, I noticed that I wasn't quite running things the right way. I updated the code as follows:
//...
var promise_flow = new Promise(function(resolve, reject){
if(post_method === 'get_me_something')
{
myModule.getSome(post_url)
.then(function(data){
resolve(data);
})
.catch(function(err){
reject(err);
});
}
else
{
resolve('nothing');
}
});
//...
This way, I think it fits with the true spirit of Promises.
Your getSomething function doesn't return a promise. Make it returns a promise, and fulfill the promise in response.on('end').
function getSomething(url_str)
{
return new Promise(function(resolve, reject) {
var callback_fn = function(response){
var body = '';
response.on('data', function (data) {
body += data;
});
response.on('end', function () {
//console.log(body);
resolve(body);
});
};
https.request(url_str, callback_fn).end();
});
}
Then in your main file, call it like this : myModule.getSomething(post_url).then(resolve);.
I use the following code which is working OK
var ncp = require('ncp').ncp;
function load(folderPath) {
ncp.limit = 16;
var path = require('path');
var localPath = path.join(__dirname, '../pl');
ncp(folderPath, localPath, {dereference: false}, function (err) {
if (err) {
return console.error(err);
}
console.log('done to save the files!');
});
};
I want to use promise instead of callback but when using the following I got error
var Promise = require('bluebird');
var ncp = Promise.promisifyAll(require('ncp').ncp);
function load(folderPath) {
ncp.limit = 16;
var localPath = path.join(__dirname, '../pl');
ncp(folderPath, localPath, {dereference: false})
.then(function (result) {
console.log('done to save the files!');
})
.catch(function (err) {
console.err(err);
});
};
The error is :
TypeError: Cannot read property 'then' of undefined
Promise.promisifyAll() is used to promisify objects. It will iterate the object and promisify all of the function properties on that object.
However, ncp is a function itself, it doesn't have properties, so promisifyAll() won't do. You're looking for promisify() which takes a function as an argument, and returns a promisified function.
So all you need to do is change
var ncp = Promise.promisifyAll(require('ncp').ncp);
to
var ncp = Promise.promisify(require('ncp').ncp);
I've used the following code to call two modules, but the invoke action is called before the validate file (I saw in debug). What I should do to verify that validateFile is called before appHandler.invokeAction? Should I use a promise?
var validator = require('../uti/valid').validateFile();
var appHandler = require('../contr/Handler');
appHandler.invokeAction(req, res);
Update
this is the validate file code
var called = false;
var glob = require('glob'),
fs = require('fs');
module.exports = {
validateFile: function () {
glob("myfolder/*.json", function (err, files) {
var stack = [];
files.forEach(function (file) {
fs.readFile(file, 'utf8', function (err, data) { // Read each file
if (err) {
console.log("cannot read the file", err);
}
var obj = JSON.parse(data);
obj.action.forEach(function (crud) {
for (var k in crud) {
if (_inArray(crud[k].path, stack)) {
console.log("duplicate founded!" + crud[k].path);
break;
}
stack.push(crud[k].path);
}
})
});
});
});
}
};
Because glob and fs.readFile are async functions and appHandler.invokeAction is invoked during i/o from disk.
Promise is a good solution to solve this but an old school callback could do the job.
validator.validateFile().then(function() {
appHandler.invokeAction(req, res);
});
and for validate
var Promise = require("bluebird"), // not required if you are using iojs or running node with `--harmony`
glob = require('mz/glob'),
fs = require('mz/fs');
module.exports = {
validateFile: function () {
return glob("myfolder/*.json").then(function(files) {
return Promise.all(files.map(function(file) {
// will return an array of promises, if any of them
// is rejected, validateFile promise will be rejected
return fs.readFile(file).then(function (content) {
// throw new Error(''); if content is not valid
});
}));
})
}
};
If you want working with promise mz could help :)
As the fs.fileRead is async, you should put the code that you want to execute after validateFile to its callback.
The origin could be:
var validator = require('../uti/valid').validateFile();
var appHandler = require('../contr/Handler');
// create a new function that when execute, will call appHandler.invokeAction with req and res given to its arguments.
validator.validateFile(appHandler.invokeAction.bind(null, req, res));
The validator part should be:
var called = false;
var glob = require('glob'),
fs = require('fs');
module.exports = {
validateFile: function (callback) {
glob("myfolder/*.json", function (err, files) {
var stack = [];
// Use it to decide whether all files processed or not.
var filesToLoad = files.length;
files.forEach(function (file) {
fs.readFile(file, 'utf8', function (err, data) { // Read each file
--filesToLoad;
if (err) {
console.log("cannot read the file", err);
// If the invoke action doesn't depend on the result. You may want to call it here too.
}
var obj = JSON.parse(data);
obj.action.forEach(function (crud) {
for (var k in crud) {
if (_inArray(crud[k].path, stack)) {
console.log("duplicate founded!" + crud[k].path);
break;
}
stack.push(crud[k].path);
}
})
// Only called the callback after all files processed.
if (filesToLoad === 0) {
callback();
}
});
});
});
}
};
Edit: Thanks for Bergi's remind that there's the files are an array and you have to call the callback when all files is processed, so we have to further use a variable to decide how many files are not processed yet.
I'm learning node, its events API and trying to make a simple example.
So here's my code:
var fs = require('fs');
var util = require('util');
var events = require('events');
var FileLoader = function () {
events.EventEmitter.call(this);
this.load = function (url) {
fs.readFile(url, function (err, data) {
if (err) {
throw err;
} else {
this.emit('loaded', data.toString());
}
});
};
};
util.inherits(FileLoader, events.EventEmitter);
module.exports = FileLoader;
And I want to load() a text file and when it is loaded, catch it with .on('loaded',function(){...}), but this is undefined, so program crashes.
I'm definitely missing something, how to make it work?
This is not an issue with Node, it is an issue with JavaScript. The this in this.emit is not a FileLoader instance.
I recommend you read up on the behavior of this in JavaScript. The MDN docs for this may be helpful for you.
In this case, you need to bind the this inside your readFile callback so that the inner this is the outer this by adding .bind(this) to your callback.
this.load = function (url) {
fs.readFile(url, function (err, data) {
if (err) {
throw err;
} else {
this.emit('loaded', data.toString());
}
}.bind(this));
};