casperjs console.log() not printing correct value [duplicate] - javascript

This question already has an answer here:
CasperJS posting only the last item multiple times from my for-loop
(1 answer)
Closed 6 years ago.
Im using phantomjs and casperjs to test out my website.
In my JavaScript I have a for loop using var i, and another var rando inside the loop which increments.
I am calling
console.log(i);
console.log(rando);
for testing, but it does not print the correct value to the command line. However, I know that the variable is changing, as the code does as intended, for example the for loop is incrementing.
I tried using
console.log("%d", i);
but no luck.
My Code using a suggested solution still no luck:
for (i=0; i<100000; i++) { //// first loop
casper.wait(13000, function () {
casper.then(function () {
console.log(String.valueOf(i));
console.log(String.valueOf(rando));
casper.click(x(('/html/body/div[2]/div/div[2]/div/article/div[2]/section[2]/a/span')));
console.log(timeStamp());
});
});
casper.then(function () {
casper.click(x(('/html/body/div[2]/div/div[1]/div/div/a[2]')));
});
if (rando == 14) {
casper.then(function () {
casper.click(x(('/html/body/div[2]/div/div[2]/div/article/header/span/button')));
console.log(timeStamp());
});
rando = 0;
} else {
rando++;
}
}
The result is printing i as 100000 and rando as 10 every time, even when they are incremented.

Try to use
console.log(String.valueOf(i))
If didn't work please show your code

You are probably not handling the promises that the methods to get values from the DOM return.
"Crawling" frameworks massively use promises, to retrieve values from the DOM, because most of the times those values are changed dynamically and are not available as soon as the DOM is retrieved to the client.
more info about Promises here
This is an old gist i made using webdriverjs wich is similar to casper/phantom: link to gist. I specifically made this gist to pratice promise handling.

Related

Javascript setTimeout Calls To Add Dom Nodes All Getting Added At The Same Time [duplicate]

This question already has answers here:
How do I add a delay in a JavaScript loop?
(32 answers)
Closed 4 years ago.
I'm trying to have a 2 second delay in between when some p tags get added to the dom. This is what I've got so far.
var inputs = ['blah','blah blah', 'blah blah blah'];
function insertInput(input){
inputs.forEach(function(input){
var commandP = document.createElement('p'),
textNode = document.createTextNode(input),
newInput = document.getElementById('first_input');
commandP.appendChild(textNode);
commandP.className = 'inputs';
delayInsert(newInput, commandP);
})
}
function delayInsert(input, command){
setTimeout(function(){
input.appendChild(command);
}, 2000)
}
insertInput(inputs);
Don't usually use setTimeout often so the answer isn't readily apparent to me but all the dom nodes get added at the same time. If anyone has a solution as to how to go about spacing the insertion of the nodes apart, and to explain why this behavior is occurring in the first place as I'm pretty curious now, I'd greatly appreciate it.
Your forEach calls execute at the same time (give or take) so you get multiple calls to setTimeout at the same time with the same delay, so the functions all execute at the same time.
To tweak that, you just need to modify the delay given to the setTimeout
function insertInput(input) {
inputs.forEach(function (input, i) {
// ...
delayInsert(newInput, commandP, (i + 1) * 2000);
})
}
function delayInsert(input, command, delay) {
setTimeout(function () {
input.appendChild(command);
}, delay);
}

Why are my JS Objects added to Array as undefined values? [duplicate]

This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 4 years ago.
I have a function that grabs some values from JSON files, and creates an item object.
var searchIndex = [];
function getSearchTerms(param){
var filePath = 'json/' + param + '.json';
$.getJSON(filePath, function( data ) {
var item = {
param: param,
title: data.title,
person: data.fname + ' ' + data.lname
};
// console.log(item);
// searchIndex.push(item);
return item;
});
}
I can see the item objects with correct properties being created when I check the console.
However, when I try to add the objects to searchIndex array, either within the function or within the loop that calls the getSearchTerms function, I get an array with the correct number of rows, but all the values are undefined.
var contentFiles = [ 'a', 'b', 'c'];
for (var i = 0; i < contentFiles.length; i++) {
searchIndex.push( getSearchTerms(contentFiles[i]) );
}
What stupid thing am I doing wrong here? Thank you in advance for your help.
Remember, reading files from the disk takes a little bit of time. Not a lot, but enough to mess with this little bit of code you're trying to write. Now's a good time for you to learn how to use asynchronous code. Here are some slight alterations to specific lines of code that might be able to help.
async function getSearchTerms(param)
await var item
within your loop...
await getSearchTerms(contentFiles[i])
searchIndex.push(object))
I'm no expert, and this is the first SO question I've ever answered. You might need a .next in there or something. If this doesn't work, look further into the concept of async/await functions. In your code, you're pushing objects that haven't gotten their actual value yet, because reading from disk takes a moment. JS travels line by line without waiting, and sometimes you'll have values that need a second to be sorted out.

Why is for loop setting all jquery onclick to last iteration [duplicate]

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 5 years ago.
I have the following code that works
var btns = $('.gotobtn');
$('#'+btns.get(0).id).click(function() {
document.querySelector('#navigator').pushPage('directions.html', myInfo[0]); });
$('#'+btns.get(1).id).click(function() {
document.querySelector('#navigator').pushPage('directions.html', myInfo[1]); });
$('#'+btns.get(2).id).click(function() {
document.querySelector('#navigator').pushPage('directions.html', myInfo[2]); });
// this works. I click on button 0 and get myInfo[0],
// on 1 and get myInfo[1], on 2 and get myInfo[2]
But replacing it with a loop does not work correctly. Instead, I always get the last element: myIfno[2] for any button I press.
var btns = $('.gotobtn');
var i = 0;
for (i = 0; i<3; i++){
var btnid = "#" + btns.get(i).id;
$(btnid).click(function() {
document.querySelector('#navigator').pushPage('directions.html', myInfo[i]); });
}
// this does set the buttons on-click but when I click on them,
// all get the latest iteration, in this example myInfo[2]
Why is this? And how do I fix that, without defining each button manually?
I want to see how to do it in jquery.
Because: JavaScript does not have block scope. Variables introduced with a block are scoped to the containing function or script
Replace:
$(btnid).click(function() {
document.querySelector('#navigator').pushPage('directions.html', myInfo[i]);
});
With:
$(btnid).click(customFunction(i));
And declare this function outside the loop:
function customFunction(i) {
document.querySelector('#navigator').pushPage('directions.html', myInfo[i]);
}
your
$(btnid).click(function() {
document.querySelector('#navigator').pushPage('directions.html', myInfo[i]); });
Must be outside your for loop.
#ibrahim mahrir is correct. It's caused by the same phenomena as described in 46039325 although this question is specific for JQuery binding and will probably be useful to some. (I saw the unanswered question in several places on the web)
It happens because I'm binding to i, and in the meantime i has changed to the last iteration (which is 2 in this example). I need to bind to the value of i while iterating.
This will happen (due to quirks in javascript) if I define the binding to a parameter of a function. The parameter is "dynamically created" each time and the value of that param (during that iteration) will be bound.
So when I finally do click on the second button (id:1, the first is id:0), it will invoke the method with the value of 1, correctly.
Here's an example of how the fix looks in jQuery:
$(function(){ // document ready
function btnaction(i){
var btns = $('.gotobtn');
$('#'+btns.get(i).id).click(function() {
document.querySelector('#navigator').pushPage('directions.html', gotoInfo[i]);
});
}
and I call it in the loop
for (i = 0; i<6; i++)
btnaction(i);
Alls well that ends well...

Each Loop Not Setting Variable Outside Of Its Scope [duplicate]

This question already has answers here:
Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference
(7 answers)
Closed 5 years ago.
I'm trying to check that a list of elements from a column on a webpage are in descending order. I come from a Java background so I'm still trying to figure out why this wont work. To my knowledge when it hits that final expect, isDescending should be properly set, but instead it never changes from what I initialize it as.
I've put print statements in the loop previously and everything is working fine in there.
I did some digging and it probably has to do with either it running the expect at the bottom before it finishes the loop, or isDescending is not actually getting set properly for some reason.
I tried a number of things like setTimeout and others but nothing fixed the issue.
Here is the below code, looking for any suggestions or link to pages where I can research more.
function checkIfDescending(){
// Will hold list of webelements that each have one account number
const accountNumbersDescending = pa_search.account_no_column_elements();
let previousDescendingValue = '';
let isDescending = true;
accountNumbersDescending.each((accountNumberElement) => {
accountNumberElement.getText().then((text) => {
if (previousDescendingValue !== '') {
if (!(text >= previousDescendingValue)) {
isDescending = false;
}
}
previousDescendingValue = text;
});
});
expect(isDescending).toBe(true);
}
As a final note, if I put the expect within the if statement, it properly works. Problem is that it then will run several times if there is a failure early on.
Thanks to Mark Schultheiss for leading me to this answer.
I'm not 100% it's the right way to do things but it probably passes/fails now.
function checkIfDescending(){
const accountNumbersDescending = pa_search.account_no_column_elements();
let previousDescendingValue = '';
let isDescending = true;
accountNumbersDescending.each((accountNumberElement) => {
accountNumberElement.getText().then((text) => {
if (previousDescendingValue !== '') {
if (!(text >= previousDescendingValue)) {
isDescending = false;
}
}
previousDescendingValue = text;
})
}).then(() => {
expect(isDescending).toBe(true);
});
}
You can take a look at chai-increasing plugin.
You need to store values in an array or a promise and make an expect assertion.

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);
});
});

Categories

Resources