I am using the JavaScript testing framework, Protractor.
I have an external utility class file that I am using to organize common logic within my test specs. See Using Page Objects to Organize Tests.
There seems to be an issue when I take a block of code that resolves a promise, and put in into my Helper utility class file, for the same use.
Example:
In an individual spec file, this code works as expected:
test.spec.js:
it('should display the Protractor workflow text', function() {
// match variable
var match = false;
// get elements by tag
var scanElements = element.all(by.css('div')).each(function(el) {
el.getText().then(function(text) {
var sp = text.split(' '); // split by <space>
for (var i = 0; i < sp.length; i++) {
if (sp[i] == 'Protractor') {
match = true;
}
}
});
});
// on complete
scanElements.then(function() {
console.log(match); // returns true
});
});
However, if I separate this logic into my Helper utility class, the return value is undefined.
Helper.js
/** containsText [process]
* searchs for text in tag elements (i.e. 'string' in <a>, 'string' in <div>)
* #param string - tag name to limit
* #param string - string value to search against
*
* #return boolean - if string is found
*/
this.containsText = function (tagName, stringValue) {
// match variable
var match = false;
// get elements by tag
var scanElements = element.all(by.css(tagName)).each(function(el) {
el.getText().then(function(text) {
var sp = text.split(' '); // split by <space>
for (var i = 0; i < sp.length; i++) {
if (sp[i] == stringValue) {
match = true;
}
}
});
});
// on complete
scanElements.then(function() {
return match;
});
}
test.spec.js
it('should display the Protractor workflow text', function() {
console.log(helper.containsText('div', 'Protractor')); // does not wait to resolve, returns undefined
});
Edit: All of the links to similar questions on Stack Overflow have to deal with asynchronous calls via Ajax requests. I'm looking for a solution for the Protractor / Webdriver framework. I have read documentation on creating promises with Protractor, and have tried the following as well:
Helper.js
this.containsText = function (tagName, stringValue) {
// get elements by tag
var scanElements = element.all(by.css(tagName)).each(function(el) {
el.getText().then(function(text) {
var sp = text.split(' '); // split by <space>
for (var i = 0; i < sp.length; i++) {
if (sp[i] == stringValue) {
return true; // fufill outer promise and break execution
}
}
});
});
return scanElements;
}
test.spec.js
helper.containsText('div', 'Protractor').then(function(f) {
console.log(f);
});
Still looking for some assistance.
Related
I am new mocha and I need to run one test 100 times. In the following code, a function regexTest is tested for true.
var assert = require('assert');
describe('regexTest',function() {
it('should return true if the string starts with a number',function() {
var i = 1;
while(i++ <= 100) {
assert.equal(true,regexTest(Math.random().toString(36).substring(7)));
}
});
})
function regexTest(randStr) {
let match = randStr.match(/^\d+/);
if(match && match.length > 0) {
return true;
}else{
return false;
}
}
I am not sure if this is the correct way to run a repeated test, inside a while loop. Also, I doubt the results because no matter what, it always returns passed for a single test.
What could be a better approach here?
So first off , your code seems correct and running it locally returns:
~/P/test ➤ node_modules/.bin/mocha x.js
regexTest
1) should return true if the string starts with a number
0 passing (10ms)
1 failing
As far as I remember there is no official way to repeat an assert statement, so running it inside a loop should be fine.
You might want to consider adding a more verbose failure message to your assert statement though, something like:
while(i++ <= 100) {
let testStr = Math.random().toString(36).substring(7);
assert.equal(true, regexTest(testStr), 'Test failed on ' + testStr);
}
(and just in case it helps, you don't need that if block in your regexTest function. Simply end with return match && match.length)
If you assert at every single execution of the loop, test will end at first assertion error. If you want to see how many times your test failed out of 100, you can store test results in an array, you can do something like:
var assert = require('assert');
describe('regexTest',function() {
it('should return true if the string starts with a number',function() {
var i = 1;
var errorsArray = [];
while(i++ <= 100) {
var randomNumber = Math.random();
if(!randomNumber.toString(36).substring(7)){
errorsArray.push(randomNumber);
}
}
assert.equal(0, errorsArray.length, errorsArray);
});
})
The problem with the code you have is that you are actually only testing the last random string generated.
Also, as mentioned by #Geotob, it will nicer if you print the string on which the test case failed.
You could frame it like:
var assert = require('assert');
function makeTest() {
describe('regexTest', function () {
var i = 0;
while (i++ <= 20) {
it('should return true if the string starts with a number', function () {
var i = 0;
let str = Math.random().toString(36).substring(7);
let output = regexTest(str);
assert.equal(true, output, `Failed on ${str}`);
});
}
});
}
function regexTest(randStr) {
let match = randStr.match(/^\d+/);
if (match && match.length > 0) {
return true;
} else {
return false;
}
}
makeTest();
This should work fine.
Notes:
Dynamically generating tests from the mocha doc.
So I'm currently working on a project where I'm making a http request with angular to around 1500 URLs looking for the json that matches with the condition I have (only 1 of the URLs will match). I currently have an implementation that sometimes work (but isnt deterministic I'm assuming because it the requests are asynchronous although it might just be a bug??). I'm still kinda new to angular so I'm not sure if I'm doing it correctly at all so I'm open to changing the code entirely!
this.matchingurl;
this.data;
this.findUrl = function(condition) {
var that = this;
for (var i = 0; i <= ; i++) {
// this is just looping through the url list
for (var i = 0; i < urlList.length; i++) {
for (var j = 0; j < urlList[i]['list'].length; j++) {
this.url = 'http://' + urlList[i]['list'][j] + restofurl;
var tempUrl = urlList[i]['list'][j];
$http.get(this.url).success(function(data) {
if (condition is met in data) {
that.matchingurl = tempUrl;
return;
}
})
.error(function(data){
// error handling
});
}
}
}
}
TLDR: matchingUrl isn't what I expect? Still goes inside the "condition" loop but doesn't spit out right url. Always gives me the same "url" for any sublist, right or wrong.
I would suggest that you use $q promise of angularjs to do the task, either you can check one url at a time serially( slow if you ask me), or get all results at a time by requesting parallely. Below, I have done a crude implementation of the latter
this.findUrl = function(condition) {
var urls =[], self = this, oUrl; // collect all the urls
urlList.forEach(function(list){
list.forEach(function(url){
oUrl.push(url);
urls.push('http://' + url + restofurl); // not sure where you are getting this restofurl from...
});
});
$q.all(urls.map(function(url){
return $http.get(url); // returns promise for each url, thus mapping all urls to promise.
})).then(function(datas){
datas.some(function(data, i){
if(data == condition){ // change as per requirement
self.matchingurl = oUrl[i];
return true;
}
})
});
}
Edit:
same thing done checking one url at a time:
this.findUrl = function(condition) {
var urls =[], self = this, oUrl; // collect all the urls
urlList.forEach(function(list){
list.forEach(function(url){
oUrl.push(url);
urls.push('http://' + url + restofurl); // not sure where you are getting this restofurl from...
});
});
function check(i){
function fail(){ // move to check the next url in the array
i++;
if(i<urls.length) return check(i);
console.log('none of the urls are matching');
}
return http.get(urls[i]).then(function(data){
if(data == condition){ // change as per requirement
self.matchingurl = oUrl[i];
}else{
fail();
}
}).catch(fail);
}
check(0); // start the chain
}
You are right, this you could run into trouble due to the synchronous http calls if you don't handle your variables correctly. Here is a snippet to achieve the same using synchronous http calls.
this.matchingurl;
this.data;
this.findUrl = function(condition, i, j) {
var that = this;
this.url = 'http://' + urlList[i]['list'][j] + restofurl;
var tempUrl = urlList[i]['list'][j];
$http.get(this.url).success(function(data) {
if (condition is met in data) {
that.matchingurl = tempUrl;
return;
}
else{
if(urlList[i]['list'].length > j + 1){
j++;
}
else{
if(urlList.length > i+1){
i++;
j=0;
}
else{
return;
}
}
this.findUrl(condition, i, j);
}
})
.error(function(data){
// error handling
});
}
}
}
}
this.findUrl(condition, 0, 0);
I have a script, in which I am adding custom URL to the browser back button
for this I used this particular script
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
<script type="text/javascript" src="js/jquery.cookie.js"></script>
<script type="text/javascript" src="js/native.history.js"></script>
<script type="text/javascript" src="js/go.new.js"></script>
<script>
$(window).load(function()
{
setTimeout(backtrap, 100);
if (!($.cookie("clkd"))){
$.cookie("clkd", 1, { expires : 14 , path: '/'});
}
setInterval(toggle_image, 1000);
});
window.onpageshow = function (e) {
if (e.persisted) {
location.reload();
}
};
window.onpopstate = function(event) {
if (document.location.toString().indexOf("redir=1")>0){
window.location.href = "<?=$$last_offer?>";
//window.location.href = "<?=$szLinkURL?>";
}
};
function toggle_image()
{
var height = $('#banner-img').height();
$('#banner-img').toggle();
$('#loader-img').toggle();
$('#loader-img').css({'height':height+'px'});
}
function show_star(rate,id)
{
$('#al-stars-'+id).html('<span class="stars" id="stars-'+id+'">'+parseFloat(rate)+'</span>');
$('#stars-'+id).stars();
}
$.fn.stars = function()
{
return $(this).each(function() {
$(this).html($('<span />').width(Math.max(0, (Math.min(5, parseFloat($(this).html())))) * 16));
});
}
</script>
Now the script seems to work fine, but I face a new proble, that is:
when I am clicking the refresh button on the browser, the page is rediecting to bing.com
but I dont know why this is happening...
in case if needed here's the script in the go.new.js
/**
* go.js
*
* Functions for go.php and the backtrap
*/
// --------------------------------------------------------------------------------------------------------------------
// Code for parsing query string gratefully lifted from http://unixpapa.com/js/querystring.html, with an
// addition for modifying/generating new query strings.
// Query String Parser
//
// qs= new QueryString()
// qs= new QueryString(string)
//
// Create a query string object based on the given query string. If
// no string is given, we use the one from the current page by default.
//
// qs.value(key)
//
// Return a value for the named key. If the key was not defined,
// it will return undefined. If the key was multiply defined it will
// return the last value set. If it was defined without a value, it
// will return an empty string.
//
// qs.values(key)
//
// Return an array of values for the named key. If the key was not
// defined, an empty array will be returned. If the key was multiply
// defined, the values will be given in the order they appeared on
// in the query string.
//
// qs.keys()
//
// Return an array of unique keys in the query string. The order will
// not necessarily be the same as in the original query, and repeated
// keys will only be listed once.
//
// QueryString.decode(string)
//
// This static method is an error tolerant version of the builtin
// function decodeURIComponent(), modified to also change pluses into
// spaces, so that it is suitable for query string decoding. You
// shouldn't usually need to call this yourself as the value(),
// values(), and keys() methods already decode everything they return.
//
// Note: W3C recommends that ; be accepted as an alternative to & for
// separating query string fields. To support that, simply insert a semicolon
// immediately after each ampersand in the regular expression in the first
// function below.
function QueryString(qs)
{
this.dict= {};
// If no query string was passed in use the one from the current page
if (!qs) qs= location.search;
// Delete leading question mark, if there is one
if (qs.charAt(0) == '?') qs= qs.substring(1);
// Parse it
var re= /([^=&]+)(=([^&]*))?/g;
while (match= re.exec(qs))
{
var key= decodeURIComponent(match[1].replace(/\+/g,' '));
var value= match[3] ? QueryString.decode(match[3]) : '';
if (this.dict[key])
this.dict[key].push(value);
else
this.dict[key]= [value];
}
}
QueryString.decode= function(s)
{
s= s.replace(/\+/g,' ');
s= s.replace(/%([EF][0-9A-F])%([89AB][0-9A-F])%([89AB][0-9A-F])/g,
function(code,hex1,hex2,hex3)
{
var n1= parseInt(hex1,16)-0xE0;
var n2= parseInt(hex2,16)-0x80;
if (n1 == 0 && n2 < 32) return code;
var n3= parseInt(hex3,16)-0x80;
var n= (n1<<12) + (n2<<6) + n3;
if (n > 0xFFFF) return code;
return String.fromCharCode(n);
});
s= s.replace(/%([CD][0-9A-F])%([89AB][0-9A-F])/g,
function(code,hex1,hex2)
{
var n1= parseInt(hex1,16)-0xC0;
if (n1 < 2) return code;
var n2= parseInt(hex2,16)-0x80;
return String.fromCharCode((n1<<6)+n2);
});
s= s.replace(/%([0-7][0-9A-F])/g,
function(code,hex)
{
return String.fromCharCode(parseInt(hex,16));
});
return s;
};
QueryString.prototype.value= function (key)
{
var a= this.dict[key];
return a ? a[a.length-1] : undefined;
};
QueryString.prototype.values= function (key)
{
var a= this.dict[key];
return a ? a : [];
};
QueryString.prototype.keys= function ()
{
var a= [];
for (var key in this.dict)
a.push(key);
return a;
};
QueryString.prototype.set = function(key, value)
{
this.dict[key] = [value];
};
QueryString.prototype.add = function(key, value)
{
if (typeof this.dict[key] == 'undefined') {
this.dict[key] = [value];
} else {
this.dict[key].push(value);
}
};
QueryString.prototype.toString = function()
{
var pieces = [];
for (var key in this.dict) {
for (var idx in this.dict[key]) {
pieces.push( encodeURIComponent(key) + '=' + encodeURIComponent(this.dict[key][idx]));
}
}
return pieces.join('&');
};
// --------------------------------------------------------------------------------------------------------------------
// Load ourselves into the user's history, with an altered query string. The 'bt' var tells us what step of the
// back trap we were LAST on. After that, we redirect ourselves to the next URL.
function backtrap() {
var qs = new QueryString();
qs.set('redir', 1);
var own_url = window.location.href;
var qs_at = own_url.indexOf('?');
var doped_url;
if (qs_at == -1) {
doped_url = own_url + '?' + qs.toString();
} else {
doped_url = own_url.substring(0, qs_at) + '?' + qs.toString();
}
History.pushState({}, '', doped_url);
History.pushState({} ,'', own_url);
alert(doped_url);
alert(own_url);
}
for better understanding
check this link and detect
http://gomotrak.com/main/click.php?c=2173&key=jm64njqpq608t19bgqi3fwfq&c2=AA09_00000&c3=[zone]
[NOTE]
The backbutton is working fine, there is no issue with the back-button...
Problem is, when i am clicking on the refresh button of the browser in the address bar, its reloading the bing page.
Also $$last_offer is absolutely fine cause its working this way
if(stripos($ua,'android') !== false)
{
$agent = 'a';
setlocale(LC_ALL, 'pt_BR.UTF-8');
$last_CSVfp = fopen("offer_android.csv", "r");
$last_offer_data = array();
if($last_CSVfp !== FALSE)
{
while(! feof($last_CSVfp))
{
$last_offer_data[] = fgetcsv($last_CSVfp,"",",");
}
}
fclose($last_CSVfp);
$last_arr_size = count($last_offer_data);
$last_offer = "offer".intval(intval($last_arr_size)+1);
}
so $last_offer becomes "offer3" for example
and $$last_offer becomes "$offer3" its like alias of a phpvariabledenoted by anotherphp variable`
Thanks for the help in advance.
I'm working on an practice assigment using Phonegap and Javascript. Long story short: I need to use Parse.com to store information about some Lego minifigures. The problem I'm having right now is due mostly to my inexperience in Javascript.
I'm working on letting the user add tags to the figures. The user enters them, separated by comma, and I then split the string. That's working OK.
Now, I need to add the tags that don't exist yet to my database. For this, I search for any tags with that description (using query.find) and then, if it exists, I don't create it, I just modify the relationship. If it doesn't exist, I create it and then modify the relationship.
My problem is: I can't seem to be able to access the tag description (the string) from within the success callback of query.find. I'm pretty sure it's because of the scope. Is there any proper way to access variables from withing a success callback, besides the results array?
My current code is as follows:
var Figure = Parse.Object.extend("Figure");
var Tag = Parse.Object.extend("Tag");
var nombre = $('#nombre').val();
var serie = $('#serie').val();
var figure = new Figure({"Name":nombre,"Series":serie});
var tags = $('#tags').val();
res = tags.split(","); //split the
figure.save().then(function() {
for (var i = 0; i < res.length; i++) { //for each tag
var query = new Parse.Query(Tag); //create the query.
query.equalTo("Description", res[i]);
query.find( {//execute query
success: function(results, res[i]) {
if (results.length > 0){ //if there are results.
var tag = results[0]; //get the tag
var relation_tag = tag.relation("figures"); //get the relation
relation_tag.add(figure); //add figure to relation
tag.save();
}
else { //if there are no results, the tag does not exist.
new_tag = new Tag({"Description":res[i]});
//ABOVE THIS LINE: res[i] is always undefined.
var relation_tag = new_tag.relation("figures"); //get the relation
relation_tag.add(figure); //add the figure
new_tag.save();
}
},
//error with query
error: function() {
alert("ERROR");
}
});
}
}, function(error) {
alert("No se pudo guardar la figura");
});
In the success callback, res[i] always is undefined, I assume that it's because of the scope.
This is a very common problem in async Javascript programming. You are doing something like this:
for (var i = 0; i < array.length; i++) {
anAsyncFunction(function(result) { // inner function
doSomethingWith(array[i]);
}
}
The problem is that in Javascript functions store outer variables by reference and not by value, which means that a function looks up the value of a variable from an outer scope, when it is executed and not when it is defined. Since the code is async the the inner function is called after the for loop completed and at this point we have i === array.length, so array[i] === array[array.length] === undefined.
To avoid this you can use an immediately invoked function expression (IIFE, pronounced "iffy"):
for (var i = 0; i < array.length; i++) {
anAsyncFunction((function(j) { // IIFE
return function innerFunction(result) { // inner function
doSomethingWith(array[j]); // j instead of i
}
})(i); // passing "value of i"
}
Because the IIFE is invoked immediately, the current value is of i is passed and stored into j and when the inner function executes it uses the correct value.
So in your case this should work:
success: (function(j) { // IIFE
return function(results) {
if (results.length > 0) {
var tag = results[0];
var relation_tag = tag.relation("figures");
relation_tag.add(figure);
tag.save();
}
else { //if there are no results, the tag does not exist.
new_tag = new Tag({"Description":res[j]}); // j instead of i
var relation_tag = new_tag.relation("figures");
relation_tag.add(figure);
new_tag.save();
}
}
})(i) // pass "value of i"
If you prefer, you can also pass the description itself instead of just the index to the IIFE (I think I would do it that way):
success: (function(description) { // IIFE
return function(results) {
if (results.length > 0) {
var tag = results[0];
var relation_tag = tag.relation("figures");
relation_tag.add(figure);
tag.save();
}
else { //if there are no results, the tag does not exist.
new_tag = new Tag({"Description":description}); // description
var relation_tag = new_tag.relation("figures");
relation_tag.add(figure);
new_tag.save();
}
}
})(res[i]) // pass description
var Tag = Parse.Object.extend("Tag");
var query = new Parse.Query(Tag);
I have a web application where I load (via ajax) a dictionary file (1MB) into the javascript array. I found the reason why the Mobile Safari crashes after 10 seconds. But now what I'm wondering is how do I get around this issue?
On the link above the answer suggest using setInterval, but this would mean I would have to have a dictionary file chunked into pieces and have them loaded one by one. This surely could be done, but I would have to make a lot of chunks taking into account the internet speed and too many requests would take forever for the page to load (and if I make the chunks too big it could happen that some mobile users wouldn't be able to download the chunk in a given 10second period).
So, my question is: has anyone encountered this kind of problem and how did you go about it? A general push in the right direction is appreciated.
edit:
This is the js code which I use to load the dictionary:
var dict = new Trie();
$.ajax({
url: 'data/dictionary_342k_uppercase.txt',
async: true,
success: function (data) {
var words = data.split('\n');
for (var i = words.length - 1; i >= 0; i--) {
dict.insert(words[i]);
}
},
error: function(){
$('#loading-message').text("Problem s rječnikom");
}
});
Trie.js:
function Trie () {
var ALPHABET_SIZE = 30;
var ASCII_OFFSET = 'A'.charCodeAt();
this.children = null;
this.isEndOfWord = false;
this.contains = function (str) {
var curNode = this;
for (var i = 0; i < str.length; i++) {
var idx = str.charCodeAt(i) - ASCII_OFFSET;
if (curNode.children && curNode.children[idx]) {
curNode = curNode.children[idx];
} else {
return false;
}
}
return curNode.isEndOfWord;
}
this.has = function (ch) {
if (this.children) {
return this.children[ch.charCodeAt() - ASCII_OFFSET] != undefined;
}
return false;
}
this.next = function (ch) {
if (this.children) {
return this.children[ch.charCodeAt() - ASCII_OFFSET];
}
return undefined;
}
this.insert = function (str) {
var curNode = this;
for (var i = 0; i < str.length; i++) {
var idx = str.charCodeAt(i) - ASCII_OFFSET;
if (curNode.children == null) {
curNode.children = new Array(ALPHABET_SIZE);
curNode = curNode.children[idx] = new Trie();
} else if (curNode.children[idx]) {
curNode = curNode.children[idx];
} else {
curNode = curNode.children[idx] = new Trie();
}
}
curNode.isEndOfWord = true;
return curNode;
}
}
This is a very common issue once you start doing processing in JS. If the Mobile Safari issue is the cause then what you want to do is figure out where the CPU time is going here.
I'm assuming it's the dict.insert() loop and not the data.split() call (that would be a bit more difficult to manage).
The idea here is to split up the dict.insert() loop into functional blocks that can be called asynchronously in a sequenced loop (which is what the setupBuildActions function does). After the first block each subsequent block is called via setTimeout, which effectively resets the function-time counter in the JS runtime (which seems to be what's killing your process).
Using the Sequencer function means you also keep control of the order in which the functions are run (they always run in the sequence they are generated in here and no two or more functions are scheduled for execution at the same time). This is much more effective than firing off thousands of setTimeout calls without callbacks. Your code retains control over the order of execution (which also means you can make changes during execution) and the JS runtime isn't overloaded with scheduled execution requests.
You might also want to check the node project at https://github.com/michiel/sequencer-js for more sequencing examples and http://ejohn.org/blog/how-javascript-timers-work/ for an explanation on setTimeout on different platforms.
var dict = new Trie();
// These vars are accessible from all the other functions we're setting up and
// running here
var BLOCKSIZE = 500;
var words = [];
var buildActions = [];
function Sequencer(funcs) {
(function() {
if (funcs.length !== 0) {
funcs.shift()(arguments.callee);
}
})();
}
// Build an Array with functions that can be called async (using setTimeout)
function setupBuildActions() {
for (var offset=0; offset<words.length; offset+= BLOCKSIZE) {
buildActions.push((function(offset) {
return function(callback) {
for (var i=offset; i < offset + BLOCKSIZE ; i++) {
if (words[i] !== null) { // ugly check for code brevity
dict.insert(words[i]);
}
}
// This releases control before running the next dict.insert loop
setTimeout(callback, 0);
};
})(offset));
}
}
$.ajax({
url: 'data/dictionary_342k_uppercase.txt',
async: true,
success: function (data) {
// You might want to split and setup these calls
// in a setTimeout if the problem persists and you need to narrow it down
words = data.split('\n');
setupBuildActions();
new Sequencer(buildActions);
},
error: function(){
$('#loading-message').text("Problem s rječnikom");
}
});
Here's an example using setTimeout to defer the actual insertion of words into your trie. It breaks up the original string into batches, and uses setTimeout to defer processing of inserting each batch of words. The batch size in my example is 5 words.
The actual batch insertion happens as subsequent event handlers in the browser.
It's possible that just breaking the words up into batches might take too long. If you hit this problem, remember you can chain setTimeout() calls, eg iterating for a while then using setTimeout to schedule another event to iterate over some more, then setTimeout again, etc.
function addBatch(batch)
{
console.log("Processing batch:");
for (var i = 0; i < batch.length; i++)
console.log(batch[i]);
console.log("Return from processing batch");
}
var str = "alpha\nbravo\ncharlie\ndelta\necho\nfoxtrot\n" +
"golf\nhotel\nindia\njuliet\nkilo\nlima\n" +
"mike\nnovember\noscar\npapa\nquebec\n" +
"romeo\nsierra\ntango\nuniform\n" +
"victor\nwhiskey\nxray\nyankee\nzulu";
var batch = []
var wordend;
for (var wordstart = 0; wordstart < str.length; wordstart = wordend+1)
{
wordend = str.indexOf("\n", wordstart);
if (wordend < 0)
wordend = str.length;
var word = str.substring(wordstart, wordend);
batch.push(word);
if (batch.length > 5)
{
setTimeout(addBatch, 0, batch);
batch = [ ];
}
}
setTimeout(addBatch, 0, batch);
batch = [ ];