asynchronous execution in protractor end to end tests - javascript

I have written a function , which is returning a value. In my main i am calling the function like this:
var fn_return_val = lesson.select_lesson(lesson1_text);
console.log("returned value is : " + fn_return_val);
And my function implementation is like(other file.js) :
module.exports = {
select_lesson:
function select_lesson(lesson_name) {
console.log('\n ************************* Lessson name: ' + lesson_name);
var desiredOption, status;
var repeter = element.all(by.repeater('item in items'));
repeter.then(function (items) {
items.forEach(function (icon) {
console.log('\n ************************* item');
icon.getText().then(function (txt) {
if (txt == lesson_name) {
desiredOption = icon;
}
})
}).then(function clickOption() {
if (desiredOption) {
var el = desiredOption.all(by.css('[ng-click="launchActivity()"]'));
var el_progress = desiredOption.all(by.css('.pna-progress'));
var abc = el.getAttribute('value').then(function (txt) {
status = txt;
return status
});
el_progress.getAttribute('style').then(function (progress) {
console.log('\n ************************* Lessson progress : ' + progress);
});
el.click();
}
});
});
}
};
The problem is function is returning "undefined" value, and the print statement console.log("returned value is : " + fn_return_val);
is executing before the function implementation
Can anyone help me on resolving this?

This is all about promises and protractor's Control Flow.
You need to resolve the promise and log the results inside then:
lesson.select_lesson(lesson1_text).then(function(fn_return_val) {
console.log("returned value is : " + fn_return_val);
});
And you also need to return from a function:
function select_lesson(lesson_name) {
...
// return here
return repeter.then(function (items) {
...
}).then(function clickOption() {
...
});
});
}

Related

TomTom API - Input has value but remains untouched

I'm using the TomTom FuzzySearch/Autocomplete API, to allow users to search for an address on a form and then the address input fields will prepopulate (with the address values), when the user selects an address.
My API call works absolutely fine and the input fields display the correct values.
The problem I have, is the input fields remain untouched, despite the fields having a value. (If I type document.getElementById("house-number-textbox").value, a value is returned).
How can I turned the input fields to be touched, when the input values are applied?
I noticed, if I type something in the input field, after my input values have been added, only then does the form register my inputs as valid/touched.
PS - The reason I am injecting my scripts is because I'm using an AB Testing tool. So despite the form/application being AngularJS, I can only manipulate the form via the AB Testing tool, (on top of the compiled codebase - hence why I'm using vanilla JS).
Here's my code:
function waitForElement(className, callBack){
window.setTimeout(function(){
var element = document.getElementById(className);
if(element) {
callBack(className, element);
} else {
waitForElement(className, callBack);
}
},2000)
};
// LOAD API SCRIPTS
function loadScript(scriptUrl) {
const script = document.createElement('script');
script.src = scriptUrl;
document.body.appendChild(script);
return new Promise((res, rej) => {
script.onload = function() {
res();
}
script.onerror = function () {
rej();
}
});
};
// RESULTS MANAGER
function appendParentSelector(parentSelector, selector) {
return parentSelector ? parentSelector + ' ' + selector : selector;
}
function ResultsManager(resultsElementSelector) {
this.resultsElement = document.querySelector(appendParentSelector(resultsElementSelector, '.js-results'));
this.resultsPlaceholder =
document.querySelector(appendParentSelector(resultsElementSelector, '.js-results-placeholder'));
this.resultsLoader = document.querySelector(appendParentSelector(resultsElementSelector, '.js-results-loader'));
}
ResultsManager.prototype.loading = function() {
this.resultsLoader.removeAttribute('hidden');
this.resultsElement.setAttribute('hidden', 'hidden');
this.resultsPlaceholder.setAttribute('hidden', 'hidden');
this.resultsElement.innerHTML = '';
};
ResultsManager.prototype.success = function() {
this.resultsLoader.setAttribute('hidden', 'hidden');
this.resultsElement.removeAttribute('hidden');
};
ResultsManager.prototype.resultsNotFound = function() {
this.resultsElement.setAttribute('hidden', 'hidden');
this.resultsLoader.setAttribute('hidden', 'hidden');
this.resultsPlaceholder.removeAttribute('hidden');
};
ResultsManager.prototype.append = function(element) {
this.resultsElement.appendChild(element);
};
ResultsManager.prototype.clear = function() {
for (var i = 0; i < this.resultsElement.children.length; i++) {
this.resultsElement.removeChild(this.resultsElement.children[i]);
}
};
waitForElement("house-number-textbox",function(){
console.log("WAIT FOR ELEMENT DONE");
window.ResultsManager = window.ResultsManager || ResultsManager;
console.log("document.getElementById(component)", document.getElementById("house-number-textbox") );
// use
loadScript('https://api.tomtom.com/maps-sdk-for-web/cdn/plugins/SearchBox/2.24.2//SearchBox-web.js')
.then(() => {
console.log('Script loaded!');
})
.catch(() => {
console.error('Script loading failed! Handle this error');
});
// use
loadScript('https://api.tomtom.com/maps-sdk-for-web/cdn/5.x/5.64.0/services/services-web.min.js')
.then(() => {
console.log('Script loaded!');
// HANDLE RESULTS
tt.setProductInfo('ABTest', '1');
// Options for the fuzzySearch service
var searchOptions = {
key: 'XXX',
language: 'en-Gb',
limit: 10,
countrySet: "GB"
};
var searchBoxOptions = {
minNumberOfCharacters: 1,
searchOptions: searchOptions
// autocompleteOptions: autocompleteOptions
};
var ttSearchBox = new tt.plugins.SearchBox(tt.services, searchBoxOptions);
document.querySelector('.tt-side-panel__header').appendChild(ttSearchBox.getSearchBoxHTML());
let componentForm = {
// streetName: "house-number-textbox",
municipalitySubdivision: "street-name-textbox",
localName: "town-city-textbox",
extendedPostalCode: "postcode-textbox"
};
function SidePanel(selector) {
// this.map = map;
this.element = document.querySelector(selector);
}
new SidePanel('.tt-side-panel');
var resultsManager = new ResultsManager();
ttSearchBox.on('tomtom.searchbox.resultscleared', handleResultsCleared);
ttSearchBox.on('tomtom.searchbox.resultsfound', handleResultsFound);
ttSearchBox.on('tomtom.searchbox.resultfocused', handleResultSelection);
ttSearchBox.on('tomtom.searchbox.resultselected', handleResultSelection);
function handleResultsCleared() {
resultsManager.clear();
}
// HANDLE RESULST
function handleResultsFound(event) {
// Display fuzzySearch results if request was triggered by pressing enter
if (event.data.results && event.data.results.fuzzySearch && event.data.metadata.triggeredBy === 'submit') {
var results = event.data.results.fuzzySearch.results;
if (results.length === 0) {
handleNoResults();
}
resultsManager.success();
console.log("results", results);
}
if (event.data.errors) {
console("event.data.errors", event.data.errors);
}
}
// RESPONSE
function handleResultSelection(event) {
if (isFuzzySearchResult(event)) {
// Display selected result on the map
var result = event.data.result;
console.log("THIS result", result);
;
resultsManager.success();
for (const component in componentForm) {
console.log("componentForm", componentForm);
document.getElementById(componentForm[component]).value = result.address[component];
document.getElementById(componentForm[component]).disabled = false;
console.log('component', componentForm[component]);
if (document.getElementById(componentForm[component]).value === 'undefined') {
document.getElementById(componentForm[component]).value = "";
}
};
if (result.address.streetNumber) {
document.getElementById("house-number-textbox").value = result.address.streetNumber + " " + result.address.streetName;
} else {
document.getElementById("house-number-textbox").value = result.address.streetName;
};
};
}
function isFuzzySearchResult(event) {
return !('matches' in event.data.result);
}
function handleNoResults() {
resultsManager.clear();
resultsManager.resultsNotFound();
searchMarkersManager.clear();
infoHint.setMessage(
'No results for "' +
ttSearchBox.getValue() +
'" found nearby. Try changing the viewport.'
);
};
document.querySelector(".tt-search-box-input").setAttribute("placeholder", "Enter your address...");
})
.catch(() => {
console.error('Script loading failed! Handle this error');
});
});

Why isn't my promise getting resolved?

I have two functions - A helper function for downloading files which is as follows
var downloadHelper = function(url, saveDir) {
var deferred = Q.defer();
setTimeout(function() {
deferred.resolve("success");
}, 2000);
return deferred.promise;
}
Now I have a list of files to be downloaded in parallel. I have the logic for that function as follows:
var downloadAll = function() {
var fileDownloadList = []
for(var key in config.files) {
var deferred = Q.defer();
var saveLocation = __base + config.localDir
downloadHelper(
config.files[key],
saveLocation
).then(function() {
deferred.resolve("downloaded: " + fileUrl);
}).catch(function(err) {
deferred.reject(err);
});
fileDownloadList.push(deferred.promise);
}
Q.all(fileDownloadList).done(function() {
console.log("All downloaded");
},function(err) {
console.log(err);
});
setTimeout(function() {
console.log(fileDownloadList);
}, 10000);
}
The done is never getting called!
For debugging purposes, I added a setTimeout that will be called after 10 seconds and what I see is that out of 2 files, the second promise is resolved and the first one is still in pending state.
Any ideas?
Thanks in advance
One way to make your code work
for(var key in config.files) {
(function() {
var deferred = Q.defer();
var saveLocation = __base + config.localDir
downloadHelper(
config.files[key],
saveLocation
).then(function() {
deferred.resolve("downloaded: " + fileUrl);
}).catch(function(err) {
deferred.reject(err);
});
fileDownloadList.push(deferred.promise);
}());
}
But since downloadhelper returns a promise, no need to create yet another one
for (var key in config.files) {
var saveLocation = __base + config.localDir
fileDownloadList.push(downloadHelper(
config.files[key],
saveLocation
).then(function () {
return("downloaded: " + fileUrl);
}));
}
You'll see I removed
.catch(function(err) {
deferred.reject(err);
})
That's redundant, it's the same as not having the catch at all

stoping async tasks running in parallel

I'm trying to run through (using foreach) an array of objects and then for each I'd like to call a function that uses request to get a file and then unzips it with zlib, but one at a time, given the nature of node this is currently done asynchronously.
I'd like it to be done something like this...
- foreach - first object
- call function for first object
- when function has completed
- go to the next object in the array
I have tried using the SYNC module to try and solve this but with no luck.
Any ideas on how I can achieve this?
// the function i am trying to run for each in sync
var downloadUnzipFile = function(mID) {
try {
// Read File
console.log("Started download/unzip of merchant: " + mID + " # " + new Date().format('H:i:s').toString());
request(linkConst(mID))
// Un-Gzip
.pipe(zlib.createGunzip())
// Write File
.pipe(fs.createWriteStream(fileName(mID)))
.on('error', function(err) {
console.error(err);
})
.on('finish', function() {
console.log("CSV created: " + fileName(mID));
console.log("Completed merchant: " + mID + " # " + new Date().format('H:i:s').toString());
//console.log("Parsing CSV...");
//csvReader(fileName);
});
} catch (e) {
console.error(e);
}
}
module.exports = function(sMerchants) {
var oMerchants = JSON.parse(JSON.stringify(sMerchants));
sync(function() {
oMerchants.forEach(function eachMerchant(merchant) {
downloadUnzipFile(merchant.merchant_aw_id);
})
})
};
var promiseQueue = (function() {
'use strict';
var promiseQueue = function() {
var queue = [Promise.resolve(true)];
var add = function(cb) {
var args = Array.prototype.slice.call(arguments);
args.shift();
queue.unshift(new Promise(function(resolve) {
queue[0].then(function() {
resolve(cb.apply(null, args));
queue.pop();
});
}));
};
return {
add: add
}
}
return promiseQueue;
}());
usage EXAMPLE:
This is the asynch function that will be called
var theFun = function (time, n) { // use whatever arguments you like that will be called with your function
return new Promise(function(resolve) {
//asynch function goes here INSTEAD of the setTimeout and it's contents, I repeat, INSTEAD of the setTimeout
setTimeout(function() { // this is for demonstrating ONLY
console.log('resolving', n, time); // this is for demonstrating ONLY
resolve(time); // this is for demonstrating ONLY
}, time); // this is for demonstrating ONLY
// remember to resolve("someValueNotImportantAsItIsntUsedAnywhere") on completion of your asynch function
});
}
This is how the items get added to the queue - I did it this way because of MY use case
var pq = promiseQueue();
for(var i = 0; i < 5; i++ ) {
var r = 1000 - i * 150;
console.log('adding ', i, r);
pq.add(theFun, r, i);
}
Hope you find this of some use
First, your function needs to take a callback so it can communicate when it has finished:
var downloadUnzipFile = function(mID, next) {
try {
// Read File
console.log("Started download/unzip of merchant: " + mID + " # " + new Date().format('H:i:s').toString());
request(linkConst(mID))
// Un-Gzip
.pipe(zlib.createGunzip())
// Write File
.pipe(fs.createWriteStream(fileName(mID)))
.on('error', function(err) {
console.error(err);
})
.on('finish', function() {
console.log("CSV created: " + fileName(mID));
console.log("Completed merchant: " + mID + " # " + new Date().format('H:i:s').toString());
//console.log("Parsing CSV...");
//csvReader(fileName);
next();
});
} catch (e) {
console.error(e);
next();
}
}
Then, we need to recursively call each one when the previous has finished:
module.exports = function(sMerchants, next) {
var oMerchants = JSON.parse(JSON.stringify(sMerchants));
var i = 0;
var run = function() {
if(i < oMerchants.length)
downloadUnzipFile(i++, run);
else
next();
};
};
Note that I also added a callback to the exported function, so it can communicate when it is finished. If this is unnecessary, you can drop it.
This may work for you, uses Promise. Need to add resolve and reject callbacks to your downloadUnzipFile-
var exports = (function () {
'use strict';
var pre = document.getElementById('out');
function log(str) {
pre.appendChild(document.createTextNode(str + '\n'));
}
function downloadUnzipFile(id, resolve, reject) {
log('Start: ' + id);
try {
setTimeout(function () {
resolve(id);
}, 3000);
} catch (e) {
reject(e);
}
}
function done(id) {
log('Done: ' + id);
}
function error(e) {
log(e.message);
}
function getPromise(mID) {
return new Promise(function (resolve, reject) {
downloadUnzipFile(mID, resolve, reject);
});
}
return function (sMerchants) {
JSON.parse(sMerchants).reduce(function (next, mID) {
if (!next) {
next = getPromise(mID);
} else {
next = next.then(function (id) {
done(id);
return getPromise(mID);
}, error);
}
return next;
}, null).then(done, error);
};
}());
exports(JSON.stringify([1, 2, 3, 4, 5]));
<script src="https://cdnjs.cloudflare.com/ajax/libs/json2/20150503/json2.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/es5-shim/4.1.7/es5-shim.min.js"></script>
<script src="https://rawgit.com/jakearchibald/es6-promise/master/dist/es6-promise.min.js"></script>
<pre id="out"></pre>
I added the browser shims to support older browsers that may be viewing this, you shouldn't need them on node.js but you may need to require a Promise shim if you are using an old node.js.

JS Execute functions in order, while passing the next function as an argument

I am trying to eliminate the "callback pyramid of DOOM" by doing this:
$$( //my function
function(next) { // <- next is the next function
setTimeout(next,1000); // simple async function
},
function(next){ // this function is the previous's function "next" argument
waitForSomethingAndReturnAValue(next, "I am a parameter!");
},
function(aValue){
console.log("My value is:" + aValue);
}
);
BUT I have been fiddling for about an hour, and my code doesn't work, any help? this is what I got so far:
function $$(){
for (a in arguments){
arguments[a] = function(){
arguments[a](arguments[Math.max(-1, Math.min(a+1, arguments.length-1))]);
};
}
arguments[0]();
}
Something like this works:
function $$() {
if (arguments.length <= 0) return;
var args = Array.prototype.slice.call(arguments); // convert to array
arguments[0](function () { $$.apply(null, args.slice(1)); });
}
$$(function(next) { alert("one"); next() }, function (next) { alert("two"); next() });
http://jsfiddle.net/Cz92w/
You can try this:
function $$(){
var i=0, ret, args = [].slice.call(arguments);
var obj = {
next: function() {
ret = args[i++].call(obj, ret);
}
};
obj.next();
}
and use it like this:
$$(
function() {
console.log(Date() + ' - Function 1');
setTimeout(this.next, 1e3); // simple async function
},
function(){
console.log(Date() + ' - Function 2');
return waitForSomethingAndReturnAValue(this.next, "I am a parameter!");
},
function(aValue){
console.log(Date() + ' - Function 3');
console.log("My value is:" + aValue);
}
);
function waitForSomethingAndReturnAValue(callback, param) {
setTimeout(callback, 2e3);
return param + param;
}
Basically, the returned value in each function is passed as the argument to the next one. And the reference to the next function is this.next.

Object doesn't support property or method 'save'

I am getting error while calling reportManager.save("/EditInitiatives.svc/SaveGridData"); method
<script type='text/javascript'>
$(function () {
$('#saveReport, #save-report-bottom').click(function () {
$.blockUI({ message: '<h4><img src="/Images/busy.gif" /> Saving your changes.<br/>This operation might impact several reports at once.<br/>Please be patient.</h4>' });
uiController.disableSaveButton();
reportManager.save("/EditInitiatives.svc/SaveGridData");
});
var reportManager = function () {
var tableData = JSON.stringify(handsontable.getData());
var input = JSON.stringify({ "input": tableData });
alert("Data" + input);
save = function(saveUrl) {
alert("save" + saveUrl);
$.ajax({
url: saveUrl,
type: 'POST',
dataType: 'json',
data: input, //returns all cells' data
contentType: 'application/json; charset=utf-8',
success: function(res) {
if (res.result === 'ok') {
console.text('Data saved');
}
$.unblockUI();
},
error: function (xhr) {
alert(xhr.responseText);
}
});
}
};
}
</script>
You can not access it since save is a Global variable and not part of reportManager.
The reason why it is global is because you are missing the var in front of the variable. That puts it into the Global Namespace. It will not magically be hooked up to the block scope of the function. You would need to use an OO approach to get that to work. Some basic ideas are
function Report() {
var x = 1;
this.save = function () {
alert("First: " + x);
}
}
var report = new Report();
report.save();
function report_2() {
var x = 1;
return {
save : function () {
alert("Second: " + x);
}
}
}
var report2 = report_2();
report2.save();
var report_3 = (function () {
var x = 1;
var returnObj = {};
returnObj.save = function () {
alert("Third: " + x);
}
return returnObj;
//return {
// save : function() {
// alert("Third: " + x);
// }
//}
})();
report_3.save();
Fiddle of Examples: http://jsfiddle.net/5Zhsq/
You have declared the save function without var keyword; so it will be declared in global scope not as function of reportManager.
Even if you put the var keyword before save function then it's not accessible from outside of reportManager function scope. to expose it to public you need kinda export it. here is a simple pattern to do it:
var reportManager = (function (self) {
function save(saveUrl) { ... } //this function is not public yet
self.save = save; //we are exporting the save function here
return self; //now we are returning an object with save function
})(reportManager || {});
reportManager.save("your-param-here"); //Now use your function the way you want

Categories