So I found this little bit of code online that works pretty well for waiting for a unique identifier to be loaded on the page before you interact with it..
this.waitForElementToBePresent = function(element) {
browser.wait(function() {
return element.isPresent();
}, 60000);
};
I am new to JS and Protractor.. I was wondering how this code could be changed to wait for the presence of an element where there are multiples. I know you use $$ for the identifier when there are multiples, but how can I change this method to recognize that so I would then do something like..
utility.waitForElementsToBePresent(myElement).get(0);
Look at Alecxe's answer on this question. I've been using it for awhile and it works perfectly. Here's my slightly modified version:
// wait for X number of elements
presenceOfAll = function (elem, num, timeout) {
var time = timeout || 5000;
console.log('Waiting for elements ' + elem.locator() + ' to have a count of ' + num);
return browser.wait(function () {
return elem.count().then(function (count) {
return count >= num;
});
}, time, 'Failed waiting for ' + elem.locator() + ' to have ' + num + ' total items');
};
Rather than making a new function I would probably just last element in a group then wait for it.
var els = elements.all(by.css("#id"));
waitForElementToBePresent(els.last());
As something to remember ther is "isPresent" and "isDisplayed", present mean that an element exists on the page, visible or not. If you want to wait for it to actually show on the page, first wait for it to be present then wait for it to be displayed.
http://www.protractortest.org/#/api?view=ElementArrayFinder.prototype.last
Related
I'm trying to fetch some data, and THEN fetch some other data. Problem is, there's a loop with a timeout in the mix, which makes everything so complicated. And it doesn't work.
In the order, what I'm triying to do:
Loop through listOfGroups and fetch one or many get request to my backend
When that is done, fetch one time another get request
So, here's my code:
var i = 0;
var arrayOfPostURL = [];
isFinished = false;
while (isFinished == false)
if(i == listOfGroups.length) {
return fetch("/update_db_fcb_groups/" + theID + "/" + arrayOfPostURL).then((response) => {
response.json().then((data) => {
this.setState({onFcbGroups: arrayOfPostURL})
isFinished = true;
});
});
}
if(i<listOfGroups.length){
setTimeout(function(){
fetch("/post_to_fcb_group/" + listOfGroups[i] + "/" + theID).then((response) => {
// console.log(response);
response.json().then((data) => {
arrayOfPostURL.push("+" + data.url)
i++;
});
});
// console.log(arr[i])
},5000)
}
}
This code even freezes the browser (Google Chrome) !
Any ideas?
It looks like you're using a while loop when you could be using a for.
var arrayOfPostURL = [];
for (let group of listOfGroups) {
setTimeout(function() {
fetch("/post_to_fcb_group/" + group + "/" + theID).then((response) => {
response.json().then((data) => {
arrayOfPostURL.push("+" + data.url)
});
});
}, 5000)
}
fetch("/update_db_fcb_groups/" + theID + "/" + arrayOfPostURL).then((response) => {
response.json().then((data) => {
this.setState({onFcbGroups: arrayOfPostURL})
});
});
Breaking your code down like this reveals a couple other issues.
Your setTimeouts will all finish around the same time. You're just queueing a bunch of fetches that will each take place 5 seconds after they were queued. If you meant to wait 5 seconds between each fetch, this is not the way to do so.
Your final fetch concatenates an array into the URL. That will end up looking something like "/your/url/+url1,+url2". Is that what you intended? It's a fairly unusual URL schema. You might want to change that call to a POST or PUT and pass in a JSON array in the body.
Because you're calling setTimeout on all of your fetches, your final fetch will actually finish when the loop completes which could be before any of the other fetches execute. You likely want to use Promise.all or something similar.
I am writing a test in Nightwatch to test pagination for a grid. I am using Page Objects and element selectors to make maintenance of the test suite easier. It seems, however, that I have run into a limitation with using element selectors within commands. The following code executes without error:
pagination() {
var lastPageNum;
var currentPageNum;
var newPageNum
return this
.getText('#pageNum1', function(result) {
currentPageNum = parseInt(result.value);
console.log('Current Page Number = ' + currentPageNum);
})
.getText('#pageNum2', function(result) {
lastPageNum = parseInt(result.value);
console.log('last Page Number = ' + lastPageNum);
if (lastPageNum >= 2) {
this.useXpath()
.waitForElementVisible('//*[#id="borderLayout_eRootPanel"]/div[2]/div/div/span[2]/button[3]', 3000)
.click('//*[#id="borderLayout_eRootPanel"]/div[2]/div/div/span[2]/button[3]')
.getText('//*[#id="borderLayout_eRootPanel"]/div[2]/div/div/span[2]/span[1]', function(result) {
newPageNum = parseInt(result.value);
console.log('New Page = ' + newPageNum);
this.assert.equal(newPageNum, currentPageNum + 1, "Assert pagination to Next page passed.");
})
} else {
console.log('Not enough rows exist in grid to test pagination, pages = ' + lastPageNum)
}
})
},
Note that I am using element selectors for the .getText commands. Here are the selectors I am using:
pageNum1: {
selector: '//*[#id="borderLayout_eRootPanel"]/div[2]/div/div/span[2]/span[1]',
locateStrategy: 'xpath'
},
pageNum2: {
selector: '//*[#id="borderLayout_eRootPanel"]/div[2]/div/div/span[2]/span[2]',
locateStrategy: 'xpath'
},
So far, so good. The issue I am running into is when I attempt to replace the xpath elements inside the .getText command with element selectors as well. Here is the code I am attempting to replace the above with:
pagination() {
var lastPageNum;
var currentPageNum;
var newPageNum
return this
.getText('#pageNum1', function(result) {
currentPageNum = parseInt(result.value);
console.log('Current Page Number = ' + currentPageNum);
})
.getText('#pageNum2', function(result) {
lastPageNum = parseInt(result.value);
console.log('last Page Number = ' + lastPageNum);
if (lastPageNum >= 2) {
this.useXpath()
.waitForElementVisible('#nextPageButton', 3000)
.click('#nextPageButton')
.getText('#pageNum1', function(result) {
newPageNum = parseInt(result.value);
console.log('New Page = ' + newPageNum);
this.assert.equal(newPageNum, currentPageNum + 1, "Assert pagination to Next page passed.");
})
} else {
console.log('Not enough rows exist in grid to test pagination, pages = ' + lastPageNum)
}
})
},
And here is the additional element selector I am using:
nextPageButton: {
selector: '//*[#id="borderLayout_eRootPanel"]/div[2]/div/div/span[2]/button[3]',
locateStrategy: 'xpath'
},
When I try to run the test after replacing the xpaths in the .getText command with element selectors, the test fails with the following error:
Timed out while waiting for element <#NextPageButton> to be present for 3000 milliseconds. - expected "visible" but got: "not found"
Is there a way to get the element selector to be visible within the .getText command function?
Since there does not appear to be a way to actually use the selectors within a command, I will post the workaround I have been using recently. It is fairly simple: in the page object, I am declaring a constant that contains the web element I want to use as a selector. This needs to be at the top of the page object before declaring the constants or variables you are using to hold the page object methods. The original snippets I posted above only contain the method, not the entire page object, but the declaration needs to happen well before that. Here is an example from the page object that contains the above method:
const nPageButton = '//*[#id="borderLayout_eRootPanel"]/div[2]/div/div/span[2]/button[3]';
const gridComands = {
//page object methods go here
}
Once you have declared the web element as a constant, you can use that constant when constructing your element selector:
nextPageButton: {
selector: nPageButton,
locateStrategy: 'xpath'
},
This will allow for easier maintenance, while sacrificing a bit of the functionality that selectors provide. You can use the constant within the page object when the selector does not work, you will just lose the utility of having the locate strategy defined, so you will need to rely on .useXpath() in the method when using xpaths for your web elements.
I have a function in JS contains a loop, that calls an AJAX call every iteration. The call to inserts checked elements into a DB and returns the results of those elements in the same page in the next section.
The problem I have is that when I check for e.g. 4 checkboxes out of 3 groupes, the only checkboxes of the last group gets added to the page. However, when I use alert(), I can see all elements.
I used setTimeout, but I got error in the code. I also added lines to give more time to AJX call, but the problem remains. So I wonder if there is a solution to slow down the code without using alert().
This is my script:
addAptitudeField : function(currentAutocompleteField, idChamp) {
var currentAutocompleteFieldBind = currentAutocompleteField;
var idChampBind = idChamp;
window.setTimeout(function() {
// Code ...
var paramDwr = {};
var newDivName = "div" + idChamp + lastValueId;
paramDwr[attributs.r_divId] = newDivName;
paramDwr[attributs.r_currentValue] = currentValue;
paramDwr[attributs.r_hiddenIdsField] = hiddenIdsField.id;
paramDwr[attributs.r_lastValueId] = lastValueId;
paramDwr[attributs.r_itemmod] = nbAptitudesCat % 2 == 0;
// setTimeout ( RepertoireDwr.ligneSuppEtSpanMessage, 1000 ) doesn't work
RepertoireDwr.ligneSuppEtSpanMessage(paramDwr, function(ajaxPage) {
divCategorie.update(divCategorie.innerHTML + ajaxPage.texte);
aptitudeAvecDetail.remetsValeursStockees();
var btnSuppression = $(newDivName).getElementsByTagName('img')[0];
btnSuppression.setAttribute("onclick", "formulaireFiche.updateCSS('" + newDivName + "');" + btnSuppression.getAttribute("onclick") + "fiche.updateCategorieSuppressionAptLieeUo(\'divCat" + currentCategorie + "\');"); });
}
//
// alert() : It works in this case.
//
// for (var i=0; i<5000000; i++) ; it doesn't work
}, 400);
}
Thank you in advance for your help and time.
I will likely be downvoted for mentioning this, because it is not a recommended procedure, but I believe every coder should have all facts.
In jQuery AJAX construct, there is option async:false, which will delay the script from continuing UNTIL the AJAX has completed processing. Needless to say, if things go wrong in the AJAX the browser could freeze. A lot depends on who your users are, and amount of traffic -- on a few of my ten-user in-house projects it was an acceptable solution.
$.ajax({
async: false,
type: 'post',
url: 'ajax/ax.php',
data: 'request=',
success: function(d){
if (d.length) alert(d);
}
});
Ref:
What does "async: false" do in jQuery.ajax()?
The better idea, however, is to look into the Promises interface, with methods like .when() and .then()
References:
https://jsfiddle.net/v86bc028/2/
http://jqfundamentals.com/chapter/ajax-deferreds#
http://digitizor.com/jquery-html-callback-function-using-promise/#
how does jquery's promise method really work?
The problem you're running into deals with asynchronous functions, or the A in AJAX. If you don't know what an asynchronous function is, there are many others who can explain it better than I can, so give that a google.
What's happening without the alert() in there is your code makes 4 sever calls, but all 4 get sent out before you get a response to any of them. With the alert() (or setTimeout), you're giving the code time to received each response to a call before the next one is made.
There are several ways you can approach this, the first way is by calling the next call after the first receives a response. The second way is to use an async function to call all 4 at once on different chains(?). I'm not the best at explaining this part, but there's plenty of code to be found on SO and online.
I think you have a more generic problem in your code, since you seem to need to delay your executions to wait till sth. else is finished, instead of getting anounced when it is done.
The line that annoys me most is this one
divCategorie.update(divCategorie.innerHTML + ajaxPage.texte);
what exactly is update doing? How is it implemented?
I assume it does sth. like divCategorie.innerHTML += ajaxPage.texte;
Wich is highly unfavorable, since the browser has to parse and rebuild, whatever there already is in divCategorie.innerHTML.
Just appending the new Markup would be better.
long way short: maybe a good hack would be to insert some hidden node as a placeholder (so you kan keep order, although the AJAX-requests may return in a different order) and replace that node with the real content, as soon as it arrives.
Kind of like this:
addAptitudeField : function(currentAutocompleteField, idChamp) {
var currentAutocompleteFieldBind = currentAutocompleteField;
var idChampBind = idChamp;
//this is done immediately, and therefore preserves the order of the loop,
//without any delays/timeouts
var placeholder = document.createElement("div");
placeholder.className = "placeholder";
placeholder.style.display = "none";
divCategorie.appendChild(placeholder);
window.setTimeout(function() {
// Code ...
var paramDwr = {};
var newDivName = "div" + idChamp + lastValueId;
paramDwr[attributs.r_divId] = newDivName;
paramDwr[attributs.r_currentValue] = currentValue;
paramDwr[attributs.r_hiddenIdsField] = hiddenIdsField.id;
paramDwr[attributs.r_lastValueId] = lastValueId;
paramDwr[attributs.r_itemmod] = nbAptitudesCat % 2 == 0;
// setTimeout ( RepertoireDwr.ligneSuppEtSpanMessage, 1000 ) doesn't work
RepertoireDwr.ligneSuppEtSpanMessage(paramDwr, function(ajaxPage) {
//convert the passed text into a DocumentFragment
var frag = fragment(ajaxPage.texte);
//replacing the placeholder with the fragment
divCategorie.insertBefore(frag, placeholder);
divCategorie.removeChild(placeholder);
aptitudeAvecDetail.remetsValeursStockees();
var btnSuppression = $(newDivName).getElementsByTagName('img')[0];
//this is also pretty horrible to me:
btnSuppression.setAttribute("onclick", "formulaireFiche.updateCSS('" + newDivName + "');" + btnSuppression.getAttribute("onclick") + "fiche.updateCategorieSuppressionAptLieeUo(\'divCat" + currentCategorie + "\');"); });
}
}, 400);
}
I think you should do some major refactoring. And take a look into Promises.
// * -> DocumentFragment
//strings/primitives are parsed as HTML-markup,
//null / undefined is ignored
//Arraylike structures are parsed recursively
var fragment = (function(container){
return function(src){
return reducer(document.createDocumentFragment(), src);
}
function reducer(frag, node){
var i, len, fc, c, r;
if(node === Object(node)){
if("nodeType" in node){
//dom nodes
frag.appendChild(node);
}else{
//Arraylike structures, like NodeLists or jQuery-Objects, or just plain Arrays
for(i = 0, len = ("length" in node && node.length)|0, r = reducer; i < len; (i in node) && r(frag, node[i]));
}
}else if(node != null) {
//strings (all primitives)
for((c=container).innerHTML = node; fc = c.firstChild; frag.appendChild(fc));
}
return frag;
}
})(document.createElement("div"));
My code works on localhost, but when I implement it on my site, it doesnt.
The error log says it's calling for an element that doesn't exist. I've reach to the conclusion that it can't see the element because the element is loaded dynamically.
The element is the class .newsitem_text, it's a div that contains a blog post. I believe that the jquery is calling for the class before the class is being loaded by the page.
Here is one example fiddle:
http://jsfiddle.net/ku6L240c
The error:
Uncaught TypeError: Cannot read property 'html' of null
47-ganhe-dinheiro-atraves-de-downloads:1093 Uncaught SyntaxError: Unexpected token ILLEGAL
The code:
<javascript>
var wordList = $(".newsitem_text").html().split(' ');
var newHtml = '';
$.each(wordList, function(index, word){
newHtml += ' ' + word;
if (index == 50) {
newHtml += '<div>Some HTML</div>'
}
})
;
$(".newsitem_text").html(newHtml);
</javascript>
How can I make the script wait until the class is loaded by the page, then it gets executed or something?
Seems you are doing dynamic HTML within JS code and immediately trying to get the just added tags. If that is the case, your code will have to wait and keep checking until browser rendered your new HTML and add the nodes to DOM, then you can query them or do something about.
The only way I found works is usint setTimeOut and keep checking then execute your last statement. I create a function below that checks wait and check for certain condition then execute a call back function.
//A function to wait until either times up, or until a pass in "funct" returns "true", which ever occured first.
//funct - callback function, to be returned true or false.
//done - an optional callback function for notify when waiting is over.
//timeout - the amount of time in million-second to wait.
//caller - optional string of caller for monitoring if multiple waiting session.
function waitToSync(funct, done, timeout, caller) {
//This is a hack synchronize to wait until funct() returns true or timeout becomes < 0.
caller = caller || '';
if ((funct === undefined) || typeof (funct) != 'function') return;
function waiting() {
if (!funct()) {
var dt = new Date();
console.log(caller + " waiting: " + dt.format('yyyy-mm-dd h:MM:ss'));
if ((timeout - 1000) > 0)
setTimeout(waiting, 1000); //1 second.
else {
console.log(caller + ': waitToSync timed out!!!');
document.body.style.cursor = 'default';
}
timeout -= 1000;
}
else {
if (done !== undefined && (typeof done === 'function'))
done();
}
}
waiting();
}
Do all you dynamic or anything to want to wait. Then call the WaitToSync
$.each(wordList, function(index, word){
newHtml += ' ' + word;
if (index == 50) {
newHtml += '<div>Some HTML</div>'
}
});
waitToSync(
function wait() { return document.getElementsByClassName("newsitem_text").length > 0; },
function dosomething() { $(".newsitem_text").html(newHtml); },
10000, //wait up to 10 seconds.
'dynamic add HTML');
You can try to execute this function at window load or at document ready
just put ur function inside this:
$(window).load(function(){
//your function here
});
or here
$(document).ready(function(){
//your function here
});
It is way to better to put the code in the end of the body tag after all your content so javascript can run after everything is loaded !
I am writing a service in AngularJS. I need to be able to test this service. I have everything working, except for one piece. Currently, the function that I want to test is defined like this:
return {
triggerError: false,
watchers: [],
createWatcher : function (options) {
var defer = $q.defer();
var watchId = Math.floor((Math.random() * 1000000) + 1);
var delay = 10000;
if (options && options.milliseconds) {
delay = options.milliseconds;
}
this.watchers.push($interval(
function() {
if (this.triggerError) {
defer.reject('There was an error watching.');
}
// Generate a random number
var randomA = Math.floor((Math.random() * 50) + 1);
var randomB = Math.floor((Math.random() * 50) + 1);
var result = { a: randomA, b: randomB };
defer.notify(result);
},
delay
));
return {
watchId: watchId,
promise: defer.promise
};
}
}
As the name implies, this function creates a watcher that will fire on an interval. The reason that it is written like this is because I need to be consistent with another framework that's in use. For that reason, I need to be able to make the following call in my code:
var watcher = myService.createWatcher({...});
watcher.promise.then(
function() { ... },
function(err) {},
function(result) {
console.log('A: ' + result.a);
console.log('B: ' + result.b);
}
)
I need to be able to write a test that will wait for the watcher to fire 10 times. Currently, I have the following:
it('should fire ten times', function(done) {
var count = 0;
var watch = myService.creationWatcher({});
watch.promise.then(
function() { console.log('here 1'); },
function(err) { console.log('here 2'); },
function(result) {
count = count + 1;
console.log(result);
}
);
interval.flush(5000);
rootScope.$digest();
});
I see it print 'here 1'. However, I'm not sure if I've written my test incorrectly, or if I'm missing something in my service. In my opinion, my service looks correct. However, I'm unsure about the test itself. Can someone please tell me what I'm doing wrong? I can't figure out why my test won't wait for the watcher to fire 10 times.
Thank you!
Your delay in the test is set too low. When you use
interval.flush(100000)
instead it should work - although it looks like your code will not run as-is, I had to make some adjustments.
I've created a fiddle to show you it works with a higher delay (with some small fixes). If you have questions like this in the future, including code in the question but also a fiddle will greatly help others helping you :-)
BTW: I'm assuming the creationWatcher() in your testing code was a typo and the actual function to be called should be createWatcher().