For Loop in a Page Object Not Running Properly - javascript

I am trying to check for the presence of a modal. If the modal is not present then it will place the value of the timer into browser.sleep(). This will give time for the modal to appear. I am having an issue with a for loop in a page object. When I run the code below I do not receive the alert and console.log messages under the if when I force a failure by getting changing the object. Also, I do not receive the Timer expired message.
from page_object file (relevant code)
editVinModal: { get: function () {
return browser.element({id: 'editableVINPart'});
}},
doEditVIN: { value: function () {
modalFailedToAppear = true;
console.log('In doEditVIN');
for(modal_timer = 0 ; modal_timer <= 30; modal_timer++) {
if (!(this.editVinModal)) {
alert('In If');
console.log('Modal failed to appear');
console.log('Under if - modalFailedToAppear: ', modalFailedToAppear);
browser.sleep(modal_timer);
console.log('under if - modal_timer: ',modal_timer);
}
else {
console.log('In else if else loop');
// console.log(browser.isElementPresent(this.editVinModal));
console.log('modalFailedToAppear: ',modalFailedToAppear);
modalFailedToAppear = false;
console.log('modalFailedToAppear: ',modalFailedToAppear);
console.log('modal_timer: ',modal_timer);
break;
}
}
if (modalFailedToAppear){
console.log("Modal is not present within the given time period. Timer has expired.");
}
this.editVinLink.click();
}},
Thanks in advance for

Looks like you're new around here. Welcome!
browser.sleep(), generally speaking, does not belong in your Protractor tests (except for debugging purposes). That's the bad news. The good news is that Protractor actually provides a function that does exactly (I think) what you're trying to do. It's called browser.wait() and it works like this:
browser.wait( function() {
return element(by.id('editableVINpart')).isPresent().then( function(present) {
return present;
});
}, 5000)
.then(function() {
element(by.id('editableVINpart')).click();
}, function() {
console.log('Element not found. :( ');
});
browser.wait() takes two arguments: first, an anonymous function, which it will execute repeatedly until it returns true; second, an amount of time to wait in milliseconds (by the way, browser.sleep() also takes a millisecond wait time, so your for loop is only waiting 465 milliseconds if it iterates all the way through, or about a half second--not very long).
Then, since browser.wait() returns a promise, just like all Protractor functions, we can attach a .then() statement to the end of it, which will execute the first passed-in function if the promise is successful, or the second passed-in function if it is not.
If you often have to wait for an element to be present (and for some reason it isn't synchronized with the Angular page load), it may be useful to you to have a reusable form of the function, like this:
var waitThenClick = function(el) {
browser.wait( function() {
return el.isPresent().then( function(present) {
return present;
});
}, 5000)
.then(function() {
el.click();
}, function() {
console.log('Element with locator: ' + el.locator + ' was not found. :( ');
});
};
Then you could just call it like this, for whatever element you need:
waitThenClick(element(by.id('editableVINpart')));
Good luck! Make sure to get good and clever with asynchronous stuff (especially promises) with problems like this. Protractor promises trip up the best of us.

Related

JavaScript: Stop a For loop with setTimeout function and this keyword

Basically this pulls a list of visible links (some have display:none because of a filter that I've done prior) and clicks individually on each one with a time delay, as each link takes about 2 seconds to process. However, sometimes the link does not process and gives an error message. When this condition applies, I want to stop this process as it will only fill the server with requests where everyone returns an error. As I usually do, I already tried to create an if (condition) { return; }, but I was not very successful with this particular function.
var x = 1,
myVar = "",
ModelA = true,
VarOne = $('#axws a.icon_x').filter(":visible");
for (i = 0; i < 100; i++) {
if (ModelA) {
$(VarOne).eq(i).each(function() {
var timeNow = time * x;
setTimeout(function(myVar) {
$(myVar).click();
}, timeNow, this);
++x;
});
}
}
Also, I just want to say that my javascript is not excellent, I learned it myself to automate some things that make my life easier (and I'm still learning). If anyone can help me and take the time to explain to me why it works that way, I'd be very grateful. I've been trying to find a solution for 4 hours and I couldn't get it to work. Thanks again.
Edit:
In response to the first comments, I already tried to use a break, but the break gives me an "Unsyntactic break" error. From what I understand from searching this forum, a break cannot be used with .each (I could be wrong).
I also tried with return, both false and true (just in case), but none of them worked, the loop catches the console.log I put before the return, but it doesn't stop the function.
I've also tried with an if else, so that it only clicks on the next one if it doesn't catch the condition, but the loop goes for the if (writing the console.log) and the else (clicking the link).
Unfortunately, it wasn't for lack of trying. I'm not really getting it.
Edit 2: (Solved)
I managed to solve it and I'll leave the answer here so that people who go through the same problem can solve it. Unfortunately I didn't find a solution to the problem here on the forum.
I think the problem with this was that I was using the this keyword which made the loop just being done in the setTimeout function.
setTimeout(function(myVar) {
$(myVar).click();
}, timeNow, this);
Basically I redid the loop so I don't have to use the this keyword:
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
} //New function to set a time out
var ModelA = true;
var VarOne= $('#axws a.farm_icon_x').filter(":visible");
var nVarOne = $('#axws a.farm_icon_x').filter(":visible").length; //new var to set the exact i length to the exact number of links
async function load () { // wrap loop into an async function for sleep to work
for (let i = 0; i < nfarm; i++) {
if (ModelA) {
$(VarOne).eq(i).each(function() {
VarOne.eq(i).click();
console.log("Model A: " + i);
});
}
var time = (Math.random() * (5000 - 2000) + 2000).toFixed(0); //var to set sleep time
await sleep(time); // then the created Promise can be awaited
}
}
load(); //loop function
}
That said, all that remains is to put the condition for the loop to stop if { condition == true}. As the loop will repeat the for and continue where the previous promise was, I just had to do, for example, this:
(...) if (ModelA && !condition) { ... }

Simple but difficult. How to force external libs functions run sequentially?

If we have two function in javascript, one slow and one fast. For example:
function slow() {
setTimeout(function() {console.log("slow finished")}, 10000);
}
function fast() {
console.log("fast");
}
And these functions don't have inside of them new structures like promisses (if we do not implement after).
How can we force these functions run in order? For example:
function run() {
slow();
fast();
}
run();
How can we force fast wait slow finishes?
I'm looking a solution that could work inside mobile application browsers, becase of a Apache Cordova project of mine.
Is there a way to do this?
An idea of mine is inject a callback function between the functions.
And this callback is called at the end of the slow function, calling the fast function.
An important thing is I can't (or would not) rewrite the code of the slow and fast functions,
because they will reside inside external libraries.
I'm looking for a solution to countorn this problem as an external observer and manager.
How can we do this?
Edit
He I was a trying to solve the problem merging the answers. No success yet.
I had changed slow but this is not really allowed. I have changed it to se what is happening with a. I couldn't get something interesting because a becomes undefined immediately and not after slow finishes...
var a = "adsfadsfadsf";
function slow() {
setTimeout(function() {console.log("slow done"); console.log("a2", window.a);}, 3000);
}
function fast() {
console.log("a3", window.a);
console.log("fast done");
}
var newSlow = function() {
return new Promise(function(resolve, reject){
window.a = slow();
console.log("a", a);
resolve("Sucess");
});
};
newSlow().then(function(resolve){fast();}, function(reject){console.log("error");});
I have tried with resolve(slow()); no sucess too.
That's a very interesting question. Well I can think of a way where if it is changing some global variable "g" to some value say "true". In that case if you can run them sequentially as,
<script>
var g = false;
var i;
function slow() {
setTimeout(function() {console.log("slow finished");g=true;}, 10000);
}
function fast() {
console.log("fast");
}
function run() {
slow();
i = setInterval(function(){check();},1000);
}
function check(){
if(g){
fast();
clearInterval(i);
}
}
run();
</script>
As in this demo
UPDATE: Something just struck me and I guess we might be able to add a callback function to slow() even if we can't access it directly.
If a function is called without parenthesis then the entire function as a content is returned as a string so we can edit that string by adding fast() to it registering that string as a function using eval().
function run() {
var myFun = slow+"";
myFun = myFun.substring(0,myFun.length-1);
alert(myFun);
myFun += "fast();}";
//to register the string "myFun" as a function
eval(myFun);
slow();
}
So basically our slow() function becomes,
function slow(){
//function code
//the appended function
fast();
}
NOTE: This will not worked in the example given above where GarouDan has deliberately added setTimeout limit to recreate a scenario where the slow() function takes longer time than the fast() function. However, in a real-world scenario I'm sure this approach would definetly work.
You could use the Promise pattern.
Promises are tailor made for situations where various parts of code may run slow or fast or complete in unknowable amounts of time (or not complete at all), while still giving you execution control.
My personal favorite library that implements the Promise pattern is RSVP.
Here is some pseudocode to give you the idea. Run an operation that may take a long time, then run one only when the first has either completed, or handle it's failure.
function doFoo() {
var promise = new RSVP.Promise(function(resolve, reject) {
// do some long-running operation, like retrieve
// data from a slow site...
if (data.Status && data.Status === 200) {
resolve(data);
} else {
reject(data.Error)
}
});
return promise;
}
function doBar() {
var promise = new RSVP.Promise(function(resolve, reject) {
// do some fast operation, like count to 10
for (i = 0; i < 10; i++) {
console.log(i);
}
resolve("");
});
return promise;
}
Now you can call:
function inOrder() {
doFoo().then(function(success) {
doBar();
}).catch (function(failure) {
console.log("oops! " + failure);
}
});
}
This runs doFoo, and ONLY runs doBar after doFoo has completed successfully. Note that you could also run doBar even if doFoo has failed.

Adding "then" methods into a $promise chain before a predefined "finally" method

Use Case
I have an $resource call that executes a then followed by a finally to clean up. While waiting for the server, the user may interact with the system and I would like to add more then methods before the finally method.
How can one add a then method to an existing $promise chain that executes before a predefined finally?
Sample Code
Below is a simplified code sample for the desired use case. Adding the then method to the existing chain could be triggered by an $on, $watch, or some routine.
function ctrl($scope, $timeout) {
var a = $timeout(function() {
console.log("Time out complete");
return this;
}, 1000).finally(function() {
console.log("Finally called!");
});
// some logic
// some events
// some stuff happens
// then something might insert this
// into the promise chain.
a.then(function() {
console.log("Another then!");
});
};
Result
Desired results:
> Time out complete
> Another then!
> Finally called!
Current results:
> Time out complete
> Finally called!
> Another then!
Demo
jsFiddle
You need to have the potential then calls in the chain from the beginning. You can return new promises from their callbacks ad infinitum, though.
var todo = [];
function checkTodos() {
if (todo.length)
return todo.shift()().then(checkTodos);
// do the chained task, and when finished come back to check for others
else
return todo = null;
}
function add(task) {
if (todo)
todo.push(task);
else
throw new Error("Sorry, timed out. The process is already finished");
}
$timeout(function() {
console.log("Time out complete");
return this;
}, 1000).then(checkTodos).finally(function() {
console.log("Finally called!");
});
// some stuff happens
// then something might insert this into the promise chain:
add(function() {
console.log("Another then!");
});
// Assuming it was fast enough.

Approach to check if callback was last fired?

I currently have a function which looks like that:
function update() {
buildUpdate(function(result) {
// send result to clients
});
}
This normally works correctly. However if I do something like:
// data state 1
update(); // this time, buildUpdate() won't take a long time
// do some work resulting in:
// data state 2
update(); // this time, buildUpdate() will take a long time
// and thus will finish after the third call
// do some work resulting in:
// data state 3
update(); // this time, buildUpdate() won't take a long time
As expected, the clients will receive three updates. However they are in the wrong order because the third call of update() did finish earlier than the second. From the clients point of view it looks like this:
Receives update calculated based on data state 1
Receives update calculated based on data state 3
Receives update calculated based on data state 2 (this update should not be sent)
Is there any design pattern or function which helps to avoid such a case?
Note: It doesn't matter if a client doesn't receive all updates. What matters is only that the last one received must be consistent with the current data state.
My idea was to generate on each invocation of update() a random ID. Afterwards I check in the callback whether its ID matches the last one that was generated. However the generation of the ID itself introduces a new async calculation and leads to much more code on each usage.
The easiest would probably be to add a callback
function update(callback) {
buildUpdate(function(result) {
// send result to clients
if (typeof callback == 'function') callback();
});
}
and do
update(function() { // when the first one finishes
update(function() { // run the second one
update(function() { // and when the second is finished, the third
update(); // and so on....
});
});
});
If you add the async middleware you would have more advanced methods available to deal with async behaviour.
My current approach works but is probably not the best solution.
Please submit an answer if you know a better way.
var outdated = function(f, cb) {
var counter = 0;
var finished = -1;
return function() {
var no = counter++;
a = [].slice.call(arguments);
a.unshift(function() {
if(no > finished) {
finished = no;
cb.apply(this, arguments);
}
});
f.apply(this, a);
};
};
Let's consider the following example:
var example = outdated(function(cb, a) {
setTimeout(function() {
cb(a);
}, a * 1000);
}, function(c) {
console.log('finished '+c);
});
example(1);
example(4);
example(2);
This will yield to the following output:
finished 1
finished 2
finished 4 is not being printed as it was called before finished 2 but ended after it.
To solve the actual problem as stated in the question, I would call the function like this:
var update = outdated(buildUpdate, function(result) {
// send update to clients
});
update();
// do some changes
update();

Wait for a recursive jQuery function to completely finish

Another one that should be simple, but is giving me trouble. I am trying to learn about jQuery's .Deferred() and .promise() functionality to delay certain actions until a recursive function is completely finished. Currently, my code is similar to the following:
function showText(setTarget, setMessage, setIndex, setInterval) {
var defer = jQuery.Deferred();
var doShowText = function (target, message, index, interval) {
if (index < message.length) {
$(target).append(message[index++]);
setTimeout(function () { doShowText(target, message, index, interval); }, interval);
}
else {
alert("Done!");
defer.resolve();
}
};
doShowText(setTarget, setMessage, setIndex, setInterval);
return defer.promise();
}
function startButtonClick() {
displayElement($("#getElement"));
showText($("#getElement > h1"), "This text will slowly write to the screen.", 0, 50).promise()
.then(alert("Finished."));
}
When this runs, "Finished" alert (which I am trying to defer) runs after the first execution of the recursive script, so it will appear when only one letter has been printed (not the intended result). The "Done" alert, however, appears correctly once all of the letters have been printed and the recursion is finished, so it seems as though my defer variable should not be resolved until then. Can anyone help me discover why the "Finished" alert is being called early here? Any assistance is appreciated!
EDIT: I realized I had accidentally posted a slightly older version of my code. It has been updated with the correct version (the behavior at run time is the same).
That's happening because you are actually executing the alert function right away instead of passing a function reference.
Do this instead:
.then(alert.bind(null, 'finished'));
Or
.then(function () {
alert('finished');
});
In the startButtonClick function you don't need to call .promise() on the result of showText, because you are already doing that inside of showText. Next, the argument to the then callback should be a function, right now you are immediately calling the alert function, not passing it as a function, which is why it is displaying immediately, so just wrap it in a function:
function(){ alert("Finished."); }
Here is a jsfiddle with the code: http://jsfiddle.net/RnLXF/
Here's a fiddle with the whole lot wrapped up in a nice little object: http://jsfiddle.net/YVZKw/3/
As plalx and ctcherry have already stated, the biggest problem was the lack of a function in your .then call.
HTML
<a href='javascript:void(0)'>Start</a>
<div id='sampleElement'>
<h1></h1>
</div>
JavaScript
$('a').on('click', function(){
new ShowText(
$("#sampleElement > h1"),
"I will slowly write text to the screen.",
50
)
.done(function(){
alert("Finished.")
});
});
function ShowText(target, message, speed)
{
me = this;
me.target = target;
me.message = message;
me.index = 0;
me.speed = speed;
me.defer = new $.Deferred();
me.interval = setInterval(function() {
me.target.append(me.message[me.index++]);
if (me.index > me.message.length) {
clearInterval(me.interval);
me.defer.resolve();
}
}, me.speed);
return me.defer;
}

Categories

Resources