How to handle Promises in do...while loop in Protractor - javascript

it('AddnewSupplier1',function() {
var i =0;
var isenabled=false;
var count=0;
element(by.css("path[d*='M20.995']")).click();
element(by.cssContainingText('div[class="mat-step-text-label ng-star-inserted"]','Supplier Maintenance')).getText().then(function(text) {
console.log(text);
}).then(function() {
do {
if (i>0) {
console.log("Clicking on NextButton");
element(by.css("button[class='mat-paginator-navigation-next mat-icon-button']")).click();
}
(element.all(by.xpath("//table[#class='mat-table']/tbody/tr/td[1]"))).each(function(webelement) {
webelement.getText().then(function(text) {
if(text=="IACE") {
count++;
console.log("Element is found");
//break;
}
});
});
var nextbutton = element(by.css("button[aria-label='Next page']"));
nextbutton.isEnabled().then(function(isEnabled) {
var isenabled=isEnabled;
console.log(isenabled);
}).then(function() {
i++;
console.log(i);
});
}
while(isenabled);
})
});
I have to check if Supplier ID "IACE" is present in the table.
For that I have written code taking all the values in the first column of the table and check using "each".
If the element is present in the first page the code works. But if it is in second page or third then I have to click on the next button. Before clicking on the next button I need to check if the button is enabled or disabled. If the button is enabled, then I click on the next button and check if the element is present in that page and so on. If the button is disabled, then it means element is not present and I have to fail the testcase.
For this I have written code below. I have used Do ...while because i the first page it has to check without condition (i.e next button is enabled or not).
The issue happening is:
I have stored the isEnabled() value in isenabled variable.I have initialised this variable to false.
But when I run the testcase, though my value is in second page, it is not going to second page. Instead it checks in the first page and stops the test. This is happening because in while(isenabled), isenabled is stored as false. I think before executing the isEnabled() function while(isenabled) is getting executed. Therefor while(isenabled) is getting false value which is initialised value.
I am unable to find where and how to resolve the promise here.

I tried adding async and await ,But when i add these it shows error (red cross mark).Di need to import anything before i add these async and await to my protractor scripts. I have done (SELENIUM_PROMISE_MANAGER: false, ) this in my configuration file.What else i need to do other than this to add async and await.

Still not sure what it is you are trying to accomplish but resolving the promise should work if you change your code like this:
}).then(async function() { //add async
do {
// your code here up until var nextbutton = see next line
var nextbutton = element(by.css("button[aria-label='Next page']"));
isenabled = await nextbutton.isEnabled(); // await isEnabled and store result in isenabled variable
console.log(isenabled);
i++;
console.log(i);
}
while(isenabled);
if you can't use async/await you could also do the following:
.then(function() {
function repeatMe() { // replace do with a function
if (i>0) {
console.log("Clicking on NextButton");
element(by.css("button[class='mat-paginator-navigation-next mat-icon-button']")).click();
}
(element.all(by.xpath("//table[#class='mat-table']/tbody/tr/td[1]"))).each(function(webelement) {
webelement.getText().then(function(text) {
if(text=="IACE") {
count++;
console.log("Element is found");
//break;
}
});
});
var nextbutton = element(by.css("button[aria-label='Next page']"));
nextbutton.isEnabled().then(function(isEnabled) {
console.log(isEnabled); // no need for isenabled variable anymore
i++;
console.log(i);
if (isEnabled) {
repeatMe(); // call repeatMe if isEnabled is true
}
});
}
repeatMe(); // replace while with calling function repeatMe once
})

Related

document.getElementById returns a null value, but only when executed within a multiline script. Returns correct element when executed by itself

I am creating a script to automate a data entry process on a server monitoring website. The site does not have an API for this specific data entry type, so I am using JavaScript to automate the mouse clicking and data entry process.
The script returns the correct document.getElementById("") value for getElement statement. However, it only returns the correct value whenever I manually execute line of individually. If I run the entire script, then the code breaks on one single line.
`var namesAndAddresses = { 'DomainName': 'IP' };
//Function to press sidebar "add device button"
function MenuFunction() {
MenuId = document.getElementById("menurow_Add/Import Devices").firstChild.nextElementSibling;
MenuId.click()
}
//Function to press "add device manually button"
function AddDeviceFunction() {
AddDeviceButton = document.getElementById("addDeviceButtonId_label");
AddDeviceButton.click();
}
//Function to add device information and then press the save button
function AddDeviceInformationFunction(domain, address) {
FQDN = document.getElementById("deviceNameId").value =
domain; //FQDN;
deviceClass = document.getElementById("deviceClassId").value =
"Switch/Router";
OS = document.getElementById("deviceOsId").value =
"Other Operating System";
ipAddress = document.getElementById("customUriId").value =
address; //DictionaryID;
licenseMode = document.getElementById("licenseModeId").value =
"Professional Mode";
saveButton = document.getElementById("cancelButtonId"); //change to save button
saveButton.click();
}
//manually wait function
function pause(milliseconds) {
var dt = new Date();
while ((new Date()) - dt <= milliseconds) { /* Do nothing */ }
}
//For loop to add each domain and ip that is listed in the dictionary
for (var [domainName, IP] of Object.entries(namesAndAddresses)) {
window.self = document;
//Function to press sidebar "add device button"
MenuFunction();
//Insert wait for sub-page to load here
pause(3000);
//Function to press "add device manually button" **THIS IS THE FUNCTION THAT RETURNS NULL**
AddDeviceFunction();
//Insert wait for objects to load here
pause(5000);
//Function to add device information and then press the save button
AddDeviceInformationFunction(domainName, IP);
//Insert wait for objects to load here
pause(5000);
};`
HTML code is below:
enter image description here
I have tried adding in a manual "wait" because window.onload does not work due to all the HTML being loaded already. I also tried adding a try/catch statement within a loop, so that it could make the attempt 10 times over a period of 10 seconds. That didn't work either.
Your code's pause function is blocking.
What this means is that since it "waits" using a while loop, it is not actually waiting but doing a lot of useless work, using up 100% of the CPU, and preventing anything else from actually loading.
Because of this, I can only presume the elements that are supposed to appear when loading do not get a chance to, meaning getElementById returns null (they do not exist yet)
Consider using something like setTimeout, which will properly wait, without preventing other work from being done.
Replace your for loop near the end with
var entries = Object.entries(namesAndAddresses)
window.self = document;
function processEntry(){
if(!entries.length){
// Done
return
}
// Get (and remove) the first element in the array
var [domainName, IP] = entries.shift()
//Function to press sidebar "add device button"
MenuFunction();
//Wait for sub-page to load using setTimeout, which will run this code after 3000ms
setTimeout(function(){
//Function to press "add device manually button" **THIS IS THE FUNCTION THAT RETURNS NULL**
AddDeviceFunction();
//Wait for objects to load here
setTimeout(function(){
//Function to add device information and then press the save button
AddDeviceInformationFunction(domainName, IP);
//Insert wait for objects to load here
setTimeout(function(){
// Done, go to next entry
processEntry();
}, 5000);
}, 5000);
}, 3000);
};
processEntry()
I would also recommend looking into promises and async/await, which can make this code look much neater:
function pause(milliseconds) {
return new Promise(function(finish){
setTimeout(finish, milliseconds)
})
}
async function processAllEntries(){
//For loop to add each domain and ip that is listed in the dictionary
for (var [domainName, IP] of Object.entries(namesAndAddresses)) {
window.self = document;
//Function to press sidebar "add device button"
MenuFunction();
//Insert wait for sub-page to load here
await pause(3000);
//Function to press "add device manually button" **THIS IS THE FUNCTION THAT RETURNS NULL**
AddDeviceFunction();
//Insert wait for objects to load here
await pause(5000);
//Function to add device information and then press the save button
AddDeviceInformationFunction(domainName, IP);
//Insert wait for objects to load here
await pause(5000);
}
}
Your pause causes the UI not to be able to render. You can make a function that uses await to look for an element. As soon as it is available it will update.
function addElem() {
const div = document.createElement('div');
div.className = 'foo';
div.textContent = 'hello';
document.body.append(div);
}
const waitForElem = (selector) => new Promise((resolve, reject) => {
const check = () => {
const elem = document.querySelector(selector);
if (elem) {
resolve(elem);
} else {
window.setTimeout(check, 1);
}
};
check();
});
window.setTimeout(addElem, 5000);
(async function() {
const el1 = await waitForElem('#bar');
el1.textContent = 'found 1';
const el2 = await waitForElem('.foo');
el2.textContent = 'found 2';
})();
<div id="bar">Test</div>

Angular: get updated records after comment added

I have a post section in which user can add comments and reply. I have applied pagination logic to post section. When page loads initial 5 records of first page, I am displaying. Then I have reload button, on its click next 5 records of second page gets fetched from api and appended (concat used) to previous result like wise happens. I am calling getallpost function every time when reload button hits, when comment/reply gets added to get updated entries/data from api.
getallpost function.
getallpost() {
this.formData = new FormData();
this.formData.append("page", this.pageNumber);
this.formData.append("perpage", this.perPageRecords);
this.postService.getAllEventsPost(this.formData).subscribe((result) => {
if (result['status'] === false) {
} else {
this.totalRecords = result.data.pagination.totalRecords;
this.lastPage = result.data.pagination.LastPage;
this.selectedpost = this.selectedpost.concat((result as any).data.data);
this.selectedpost.map((item) => {
item.show = false;
item.isReadMore = true;
item.isShow = true;
});
}
});
}
pageNumber = 1 , perPageRecords = 5 , selectedpost = [] are defined.
on Relaod button click function is as follows.
onReloadbuttonClick() {
this.pageNumber++;
this.getallpost();
}
When comment/reply gets added function is like below
onSubmit(post_id) {
this.loading = true;
if (this.f.comment.value) {
this.postService
.addPostComment(this.id, post_id, this.f.comment.value)
.subscribe((data) => {
if (data['message'] === 'Token is Expired') {
this.loading = false;
this.authService.logout();
} else {
if (data['status'] === false) {
if (data['errors']) {
console.log(data['errors'][0].comment);
}
this.loading = false;
} else {
this.form.reset();
this.getallpost();
this.notifyService.showSuccess(
'Comment added successfully!!',
''
);
this.loading = false;
}
}
});
} else {
this.notifyService.showWarning('Please enter the comment.', '');
this.loading = false;
return;
}
}
Reload button works as expected. The problem I am facing is when I add comment/reply it gets added and success message gets displayed. But the comment I added not gets shown at that point even if I am calling getallpost function.
Its because of this.selectedpost = this.selectedpost.concat((result as any).data.data); this line current page number value new updated data gets fetched and appended again and not get replaced with new updated data. Means e.g. initially there are 5 records of first page fetched. After comment added to one of the post then getallpost function gets called and 5 records of first page gets called again having respective updated post with comment added. Ideally only data should get updated and should not get appended.
Which logic should I apply to get updated records in this.selectedpost after comment/reply added in above case without reloading/refreshing page (like without using location.reload etc)?
I have tried some logics but those are not working as expected with reload button.
Please guide and help. Thanks.
I am not sure whether I understood your problem statement properly or not.
Let me know if something is not making sense.
new updated data gets fetched and appended again and not get replaced with new updated data.
Just set selectedpost = [] each time you click on reload or before calling getAllPost() or inside of getAllPost() before assigning.
Which logic should I apply to get updated records in this.selectedpost after comment/reply added in above case without reloading/refreshing page
Call getAllPost inside onSubmit after successful addition of comment to avoid reload/refresh
} else {
this.form.reset();
this.getallpost(); // You are already doing this.
this.notifyService.showSuccess(
'Comment added successfully!!',
''
);
There will be a delay obviously in getting updated details from DB using API. Hence you can show the spinner at the comment section level and populate as soon as you get the details.
I am assuming your API returns a proper response after adding comments.

Targeting dom element with jQuery not working

I am using a JS Promise to asynchronously get the user's location inside getLocation(). And then I'm making an Ajax request to the server inside postLocation().
$('#add_location_btn').on('click', function () {
if ($('#code').val().length === 0) {
window.alert('Enter a valid code!');
} else {
getLocation().then(function (pos) {
$('#addlocation_loader').attr('hidden', false); // Show loading sign
return pos;
}).then(function (pos) {
postLocation(pos);
});
}
$('#addlocation_loader').attr('hidden', true); // Hide loading sign
});
However, eventually changing addlocation_loader 'hidden' attribute to true is not working, meaning that the attribute is properly set to false but never turns true.
Edit
It's worth noting that I want to hide the loading sign after postLocation() is executed.
I have tried setting 'hidden' to true in a third then() statement, but the sign now never shows up. It seems that the show and hide statements are quickly executed after one another, which is confusing (When I comment out the hide statement the sign is normally shown, which means that both execute).
getLocation().then(function (pos) {
$('#addlocation_loader').attr('hidden', false); // Show loading sign
return pos;
}).then(function (pos) {
postLocation(pos);
}).then(function () {
$('#addlocation_loader').attr('hidden', true); // Hide loading sign
});
You are using an asynchronous function to the attribute to false. That means that probably
$('#addlocation_loader').attr('hidden', true);
is executed before
$('#addlocation_loader').attr('hidden', false);
You may have your hide/show loader backwards. Looks Like you set hidden to false when the location is returned and to true when the button is pressed.
Perhaps something like this would work:
$("#add_location_btn").on("click", function() {
const $loader = $("#addlocation_loader");
if ($("#code").val().length === 0) {
window.alert("Enter a valid code!");
} else {
$loader.attr("hidden", false); // Show when requested
getLocation()
.then(function(pos) {
$loader.attr("hidden", true); // Hide when returned
postLocation(pos);
});
}
});
If you want to hide the loader, you should do it within the .then() callback, because that is when the promise has been resolved. So what you want to do is:
Show loader before executing postLocation()
Remember to return the promise from postLocation() (which you didn't do in the code)
Hide the loader when the promise is resolved (after posting the position has succeeded)
Here is your fixed code:
// Perform async operation
getLocation().then(function (pos) {
// Show loader
$('#addlocation_loader').attr('hidden', false);
// Post location. Remember to return the promise to chain it!
return postLocation(pos);
}).then(function() {
// Hide loader when position is successfully posted
$('#addlocation_loader').attr('hidden', true);
});

Boolean variable not keeping current state

I am trying to assign a boolean to change when a class .ion-ios-pause is clicked. And if it is clicked then it should not run the player.skipTo(player.playlist.length-1); line afterwards. However, the invalid variable is always false (besides when i console.log inside the .click(). I thought that since I declared my variable outside the scope of both statements, that the variable should change states outside of the scope of the .click() function as well, but it doesn't. What am I missing?
function clickSongPlay(artist, title, url, imageURL) {
//debugger;
player.playlist.push(
{
title: title,
artist: artist,
file: url,
imageURL: imageURL,
howl: null
}
);
////HERE IS THE CODE SNIPPET/////
var invalid = false;
$(".ion-ios-pause").click(function () {
player.pause();
invalid = true;
});
if (invalid === false) {
player.skipTo(player.playlist.length - 1);
}
//////END RELEVANT CODE SNIPPET///////
}
HTML:
<div><i class="icon ion-ios-play music-control" onclick="clickSongPlay('#song.Artist','#song.Title','#song.URL','#song.Album.ImageURL', this);"></i></div>
I am not sure what you are trying to achieve but when your main function is called clickSongPlay it sets the variable invalid to false attaches the click() listener to $(".ion-ios-pause") but it does not execute it at this point and then it checks the if statement.
Your if statement is only executed when the main function is called and not after a click and whenever you call that function it will reset the value to false, so you will always end up with the if statement executing.
The easiest fix is to get the var invalid = false; and place it outside of your function. You might also want to put the whole click listener outside of the function scope.
your problem is that after click event has fired you should do your validation check too. you can do some thing like code bellow:
var invalid = false;
function clickSongPlay(artist, title, url, imageURL) {
//debugger;
player.playlist.push(
{
title: title,
artist: artist,
file: url,
imageURL: imageURL,
howl: null
}
);
$(".ion-ios-pause").click(function () {
player.pause();
invalid = true;
validationCheck();
});
validationCheck();
}
function validationCheck()
{
if (invalid === false)
{
player.skipTo(player.playlist.length - 1);
}
}

CasperJS - how to treat an 'click' inside an loop?

I have problems to treat things after 'click' inside a loop that is inside a evaluate function. I don't know another way to treat that.
'Le' Code...
links = this.evaluate(function(){
story_boxes = __utils__.getElementsByXPath('//div[#id="contentCol"]//div[#id="stream_pagelet"]/div[contains(#id,"topnews_main_stream")]/div/div//div[contains(#data-ft,"{")]');
for(x=0;x<=story_boxes.length;x++){
story_box = story_boxes[x];
boxID = story_box.getAttribute('id');
//Is this feed a sponsored?
sponsored = story_box.querySelector('a.uiStreamSponsoredLink');
if(sponsored){
console.log("SPONSORED? " + sponsored );
try{
elink = story_box.querySelector('div > div > div > div > div > div > a');
}
catch(e){
console.log("Ooops! An error occured, sorry! " + e);
}
if(elink){
console.log("FOUND IT!");
crap = setTimeout( function(){
elink.click(); //where can I treat this?
}, 1000);
break;
}
else {
console.log("NO DONUT FOR YOU!");
}
console.log("\n\n#########");
}
}
console.log("#####");
});
//... more irrelevant things
The question is how can I treat each click ? I need just the subtree of the result of the click. I don't know if I'm clear here. Probably not... :P
I suspect you're asking how to handle the result of the click.
If the click leads to a new page load, then you can simply add a waiting step after your code and let CasperJS handle the loading of the page:
casper.then(function(){
var success = this.evaluate(function(){
...
if(elink){
console.log("FOUND IT!");
crap = setTimeout( function(){
elink.click(); //where can I treat this?
}, 1000);
return true;
}
...
return false;
});
if (success) {
this.wait(1100, function(){
// TODO: do something with the loaded page
})
} else {
// TODO: do something on fail
}
})
If the click changes the existing page, then you need to wait for such a change. There are a lot of functions that CasperJS provides for such a case. All of them begin with wait. For example:
casper.then(function(){
this.evaluate(...);
}).wait(1100).waitForSelector('some new element selector', function _then(){
// TODO: do something with the loaded page
}, function _onTimeout(){
// TODO: do something on fail
})

Categories

Resources