I'm pretty new to unit testing, but I've been trying to create a script with Nightwatch that goes through the body content of a page, clicks every link and reports back whether it's broken.
I'm trying to make a for loop that iterates through each tag in the body content, counts the number of 'a' tags contained within and then clicks on each of them, but whenever I use a browser.execute command within a for loop, the script executes out of order.
Here's my code. I've thrown in a couple of console.log statements to try and figure out what's going on:
'Click Links' : function(browser) {
browser
//Count all tags (p/div) in content that aren't links
.useCss()
.execute(function() {
return document.querySelectorAll("div.field-item.even *:not(a)").length;
},
function(tags){
tag_total = tags.value;
//Loop through every tag in content & check every a tag contained within
for (var x = 1; x < tag_total+1; x++) {
console.log("x val before execute: " + x);
browser.execute(function() {
return document.querySelectorAll("div.field-item.even *:not(a):nth-child(" + x + ") a").length;
},
function(links){
console.log("x val at start of execute: " + x);
a_total = links.value;
for (var y = 1; y < a_total+1; y++) {
browser.click("div.field-item.even *:not(a):nth-child(" + x + ") a:nth-child(" + y + ")");
browser.pause(1000);
//Conditionals for on-site 404/403 links
browser.execute(function() {
return document.querySelector("meta[content='Error Document']");
},
//Grabs url if link is broken
function(result){
if (result.value != null) {
browser.url(function(result) {
console.log("BROKEN LINK: " + result.value);
});
}
});
//Go back to previous page
browser.url(process.argv[2]);
browser.pause(1000);
}
console.log("x val at end of execute: " + x);
});
console.log("x val at end of for loop: " + x);
}
})
.end()
}
The output I'm getting:
x val before execute: 1
x val at end of for loop: 1
x val before execute: 2
x val at end of for loop: 2
x val at start of execute: 3
x val at end of execute: 3
ERROR: Unable to locate element: "div.field-item.even *:not(a):nth-child(3) a:nth-child(1)" using: css selector
It seems like the for loop is running through and skipping the entire browser.execute block. After the loop finishes, the browser.execute block is then entered with x at an invalid number of 3.
Why does the browser.execute command cause the for loop to execute out of order, and can anything be done to fix it so that it runs in the order intended?
This has nothing to do with Nightwatch, but with Javascript.
When you have a loop and you call a function within that loop, the variable used in such function is saved by reference. In other words, the variable doesn't change for any of the functions.
It's easier if we go through a simple example:
This is because
i
is saved as a reference in the function. So wheni
increments at the next iteration, the referenced doesn't change but the value does.This could be easily fixed by moving the function outside of the loop:
In case you want to explore the concept a little bit more, you can have a look at this similar SO answer (which has a very similar example - ironically).