Issue validating in all rows of a table - javascript

I have a table which consist of around 50 rows and columns on a page. I want to fetch all rows from table and write a function to check if 2nd column of all words contains word 'Silver'. I tried below code but it is not working. Can someone please help where i am missing an i am not that great with Javascript promises. If match to string is found, i just want to increment a count and then return count at end.
fetchValues(SearchString){
var matchcount=0;
var count = this.ContentTable.Rows.count();
for (var i=0;i<count-1;i++) {
return this.ContentTable.element(by.css('[id*="DXDataRow'+i+'"]'))
.then((Row) =>{
return this.ContentTable.Row.element(by.css('[id*="uxSetList_tccell'+i+'_1"]'))
.then((Column)=>{
var CoinInfo = this.ContentTable.Row.Column.element(by.css('a')).getText();
if (CoinInfo.indexOf(SearchString)>=0)
{
matchcount =matchcount+1
}
return matchcount;
});
});
}
}

first of all, you're returning a value from inside your for() loop. That guarantees your loop is only ever going to be run once, and you're only ever going to examine one row. Is that what you want? (no, really, I have no idea.) Are you trying to create a lot of promises and combine their results into a single number? You might want to use Promise.all() to combine the values of all promises you create, and return a value from that meta-promise:
var myPromises = [];
for (var i = 0; i < count; i++) {
myPromises.push(this.ContentTable.element(by.css('[id*="DXDataRow'+i+'"]'))
.then(/* ...blah blah blah ... */)
);
}
// return a single value from this function containing data from all promises
return Promise.all(myPromises).then((promiseResultsArray) => {
/* calculate match count */
});
second, I think your promises themselves are written incorrectly. You're assuming that the value of i when your promise's callback is run is the same as the value of i when your promise's callback was defined. Because of how promises work in JS, that's not actually the case.
What's happening is that your for() loop creates a bunch of promises and then, at some undefined point in the future, one of these promises gets resolved and the function you passed to its .then() method gets run. This function knows about i - it's right there in the function body, after all! - but the value of i right now is equal to the value of count - 1. We're in the future now, remember, and the for() loop has terminated, and the termination condition for your loop is when i === count - 1. This will be the case for every single promise you create in this loop, because they all get executed after the loop terminates.
You can fix this in a bunch of ways. Probably the cleanest is to declare a new variable, initialized to the current value of i and never ever changed, and refer to that inside your .then() callback:
var i = 0;
var myPromises = [];
for (i = 0; i < count; i++) {
var currentCount = i;
myPromises.push(this.ContentTable.element(by.css('[id*="DXDataRow'+currentCount+'"]'))
.then(/* ...blah blah blah ... */)
);
}
// return a single value from this function containing data from all promises
return Promise.all(myPromises).then((promiseResultsArray) => {
/* calculate match count */
});
if you want more information, there are plenty of SO questions about how to use promises.

Related

Unable to get the length

I am novice to javascript concepts.
Why i am getting the different output for the same variable length?
While it is showing the expected result inside the body ?
var length;
element.all(by.className("className")).getText().then(function(items){
length = items.length;
console.log("1st output = "+length);//1
console.log("2nd output = "+items.length);//1
});
console.log("3rd output = "+length);//undefined
Output:-
1st output = 1
2nd output = 1
3rd output = undefined
Because your all() is async call so console.log will run before you finish then chain and assign value to length
The element.all(by.className("className")).getText()) call in your function returns something called a promise- it executes some code, and after this code is executed, it calls the function inside .then(). However, this happens independently of the rest of your program- so it continues to execute the console.log after the .then() statement.
This leads to what is called a race condition- where the order of operations can be undefined.
All before answers already explain what happens and why. So it's clear, that your 3rd console.log is executed before your then() condition finishes.
What you can try is this:
var length;
element.all(by.className("className")).getText().then(function(items){
length = items.length;
console.log("1st output = "+length);//1
console.log("2nd output = "+items.length);//1
});
browser.waitForAngular(); //add this line to let Protractor wait for Async tasks to be resolved first
console.log("3rd output = "+length);
However, keeping the 3rd console.log() inside the then() statement would be the more proper solution.

I can console.log my array but it's values shows up as undefined when appended to a div

I can console.log the array before and after I step through it to build list items. When I run the code I get 10 list items in my html that all read "undefined" instead of being the values I'm pulling out of my chrome history.
Any ideas as to why?
var urlarray = []
var historyResults = function () {
var microsecondsPerWeek = 1000 * 60 * 60 * 24 * 7;
var oneWeekAgo = (new Date).getTime() - microsecondsPerWeek;
chrome.history.search({
'text': '', // Return every history item....
'startTime': oneWeekAgo // that was accessed less than one week ago.
}, function(historyItems) {
for (var i = 0; i < historyItems.length; ++i) {
urlarray.push(historyItems[i].url);
}
})
console.log(urlarray)
}
historyResults()
function addElement () {
var makeUL = function(data) {
var ul = document.createElement("ul");
// create the UL
console.log(urlarray)
for (i = 0; i < 10; i++) {
var a = document.createElement('a');
a.href = data[i];
a.appendChild(document.createTextNode(data[i]));
console.log(a)
var li = document.createElement("li");
li.appendChild(a);
ul.appendChild(li);
// step through the array and create a link out of the value of the array and append them to the list item
// append the list item to the UL
}
return ul;
// return ul full of li
}
console.log(urlarray)
document.getElementById("arraylist").appendChild(makeUL(urlarray));
// go to html to find div and append the full UL with urlarray as the array
}
addElement()
You have two issues going on.
First, you are logging the array, but your browser does not log it immediately. It does so when it has the CPU available. When you log the array, its not yet populated with values. A moment later, when you expand the array in your browser console, the array is now populated because the evaluation of the array is delayed.
You can see this more clearly if you change your logging statement to: console.log(JSON.stringify(urlarray)). This forces the immediate evaluation of the object and turns it into a JSON string, which can then be written to the browser console a moment later.
For more information on the delayed evaluation of logged objects, see this question.
Okay, this brings us to your second issue. Your logging statement is executing before the callback to chrome.history.search does. That's why the array isn't yet populated. You need to use promises to ensure your code executes in the expected sequence. For this you should use a library like jQuery or Q.
I recommend reading about promises. Whichever library you use, your code will follow this basic structure:
Get a 'deferred' object. I'll call it deferred.
In your callback, resolve deferred with the array: deferred.resolve(urlarray)
Where your logging statement is, get the promise from the deferred object. Return that promise from the historyResults method.
Where you call historyResults, instead do:
historyResults.then(function(urls) {
console.log("promise was resolved!", urls);
// do things with your urls here, like add elements
});
Do things that depend on your urls here, inside this callback. If you do, your code will be guaranteed to execute when the urls array is fully populated and ready to go.
This is a big topic, so google "javascript promises" and good luck. I hope this helps to get you started in the right direction.
If you don't want to use promises:
If you don't want to use promises, you will need to do everything inside the callback to chrome.history.search. That's the only way to guarantee the array is populated.
Asynchronous code is fun.

How to assign count of number of rows to another value which can be use later in the test cases in Protractor

How to assign count of number of rows to another value which can be use later in the test cases in Protractor.
I have written following code but its not returning count value:
var rows = element.all(by.repeater('row in renderedRows'));
var row1 = rows.count().then(function(rowsn) {
return rowsn;
console.log(rowsn);
});
Now i want to use this count value in for loop
Protractor functions generally return promises, not values. So, you need to do your computation in a then or other code that resolves the promise.
You'll want to read the Protractor Control Flow doc and probably the WebDriverJS control flow doc
In your case, some thing like:
var rowsPromise = element.all(by.repeater('row in renderedRows'));
rowsPromise.count().then(function(rowsn) {
for (var i = 0; i < rowsn; i++) {
// do something with rowsPromise[i] (which is also a promise)
}
});

Protractor variable scope with promises

Background
I'm working on an Angular app which uses ng-repeat to make a table. One of the users found that the table sometimes contains duplicate entries, which I confirmed visually, then promptly wrote a Protractor test for.
The Test
Variable Scoping Issues
While writing the test, I noticed that the scope wasn't behaving in a way that I understood.
Naturally, the for-loop on line 61 has access to linkStorage (line 38), since it is in a higher scope. It logs that all of the objects have been successfully added to the object via the for-loop in the promise on line 47.
However, when I move the confirmation loop outside of the promise, say, before the expect block...
...linkStorage is an empty object.
Looping over the object finds no nested key-value pairs; it is truely empty.
Question (tl;dr)
Why is the linkStorage object populated inside the then statement, but not before the expectation?
Asynchronousity Strikes Again
The first example works is due to asynchronousity. Because the .getAttribute method is non-blocking, the code continues to run past it while it works. Therefore, the console loop is reached before the object has been populated; it's empty.
If you give the asynchronous code some time to run, maybe one second:
...linkStorage is populated.
Complete Solution
Chain multiple promises together to ensure code runs at the correct time.
it('should not have duplicates within the match grid', function() {
// Already on job A, with match grid shown.
var duplicate = false;
var linkStorage = {};
// Save unique links
var uniqueUserLinks = element.all(by.css('div.row table tbody tr td a'));
// get an array of href attributes
uniqueUserLinks.getAttribute('href')
.then(function(hrefs) {
// add the links to the linkStorage object
for (var i = 0; i < hrefs.length; i++) {
// if the link is already there
if( linkStorage[ hrefs[i] ] ) {
// update its counter
linkStorage[hrefs[i]] += 1
duplicate = true;
// there's already one duplicate, which will fail the test
break;
} else {
// create a link and start a counter
linkStorage[hrefs[i]] = 1;
}
};
}).then(function() {
// confirm links have been added to storage
for(var link in linkStorage) {
console.log('link:', link );
console.log('number:', linkStorage[link] );
}
}).then(function() {
expect(duplicate).toBe(false);
});
});

understanding setInterval in javascript

I have a function which does something async like saving to database. Want a mechanism that first inserts the row and the next insertion should occur only when the first insert operation has finished.
Here is what I have tried and it somewhat works.
var interval = true;
function insert() {
model.save(function () {
interval = true;
})
}
foreach(row, function (key, val) {
var interval1 = setInterval(function () {
if (interval) {
insert();
interval = false;
clearInterval(interval1);
}
}, 100)
})
Is it the correct approach of doing this? Please shed some light about my understanding of timers in javascript.
No, you should not be creating timers to poll for when something is done. That's probably the worst way you can do it. What you want to do is to explicitly start the next iteration each time the previous one finishes.
Here's the general idea for how you do this without polling. The idea is that you need to create a function that can be called successive times and each time it's called, it will perform the next iteration. You can then call that function from the completion handler of your async operation. Since you don't have a nice convenient foreach loop to control the iteration, you then have to figure out what state variables you need to keep track of to guide each iteration. If your data is an array, all you need is the index into the array.
function insertAll(rows) {
// I'm assuming rows is an array of row items
// index to keep track of where we are in the iteration
var rowIndex = 0;
function insert() {
// keep going as long as we have more rows to process
if (rowIndex < rows.length) {
// get rows[rowIndex] data and do whatever you need to do with it
// increment our rowIndex counter for the next iteration
++rowIndex;
// save and when done, call the next insert
model.save(insert)
}
}
// start the first iteration
insert();
}
If you don't have your data in an array that is easy to step through one at a time this way, then you can either fetch each next iteration of the data when needed (stopping when there is no more data) or you can collect all the data into an array before you start the operation and use the collected array.
No, this is absolutely not the right way to do this. Lets assume that row contains 10 values, then you are creating 10 independent timers which continuously run and check whether they can insert. And it's not even guaranteed that they are executed in the order they are created.
As jfriend00 already mentioned, you should omit the "loop" and make use of the completion callback of the save operation. Something like this:
var rows = [...];
function insert(rows, index) {
index = index || 0;
var current_element = rows[index];
model.save(function() {
if (index < rows.length - 1) {
insert(rows, index + 1);
}
});
}
insert(rows);
Notice how the function calls itself (somehow) after the save operation is complete, increasing the index so the next element in the array is "saved".
I would use a library that handles async stuff such as async.js
BTW it seems like your model.save methods takes a callback, which you can use directly to call the insert method. And if the insert function is one you have made by yourself, and not a part of some bigger framework, I will suggest to re-write it and make take a callback as parameter, and use that instead of using setInterval for checking when async work is done.

Categories

Resources