setTimeout with loop issue with field update - loops

I'm trying to loop through a list on a timer (currently 1 second but I want it faster). The problem is that the current value isn't being updated visually & I can't see why.
When I loop through with Firebug it works as intended but without firebug it's not showing the text change...is it skipping the text update somehow?
I set the timer to 1 second; surely a .html() call wouldn't take longer? Thanks for any info.
My HTML is simply:
<div id="winner2"><h1></h1></div>
<div id="button"">
<img id="start" src="start.png" />
<img id="stop" src="stop.png" />
</div>
My JS is:
var people = ["a", "b", "c"];
var counter = 1;
var run;
$(document).ready(function(){
$("#winner2 h1").html("a");
$("#stop").hide();
$( "#start" ).click(function() {
$("#start").hide();
$("#stop").show();
run = setTimeout (nameIncrement(),1000);
});
$( "#stop" ).click(function() {
$("#stop").hide();
$("#start").show();
clearTimeout(run);
});
function nameIncrement() {
if(counter == people.length) {
counter=0;
}
$("#winner2 h1").html(people[counter]);
counter++;
run = setTimeout (nameIncrement(),1000);
}
});

You are calling nameIncrement() and passing its return value to setTimeout(), rather than passing nameIncrement.
Remove the parentheses:
run = setTimeout(nameIncrement, 1000);
jsfiddle

It looks like you are running a recursive function without defining exit condition. this probably causes overload to the browser which decided not to run the function.
Try:
function nameIncrement() {
if(counter == people.length) {
counter=0;
return;
}
$("#winner2 h1").html(people[counter]);
counter++;
run = setTimeout (nameIncrement(),1000);
}
});
on debug mode, however, the browser is less defensive, so you can see your erors yourself.

Related

Wait for AngularJS Service to finish running (chrome console)

I'm manipulating some angular services/functions via Chrome console. (I have to specifically do this for a task I'm working on).
What I want to do is wait for the AddBagIfLimitNotReached() function to execute and finish running. And only then access the variable this.option.Quantity.
angular.element(document.querySelector(".quantity-button")).controller()._registeredControls[1].Scope.AddBagIfLimitNotReached = async function(n) {
console.log("tthis", this)
if (this.HasReachedMaximumBaggageAllowance()) {
angular.element(document.querySelector(".quantity-button")).controller()._registeredControls[1].LuggageDrawersService.OpenLuggageLimitReachedDrawer();
return;
}
this.AddBag(n);
console.log("Quantity", this.option.Quantity);
};
With this function, I'm adding a product to my basket. And this.option.Quantity should console.log 1. But it actually consoles.log 0.
However, if I check the object itself, it shows 1.
So I think what is happening, is I'm console.logging my bag quantity, before the bag has actually finished being added to the basket.
For example, if I added a settimeout of 2 seconds, the correct bag value = 1 is console.logged.
angular.element(document.querySelector(".quantity-button")).controller()._registeredControls[1].Scope.AddBagIfLimitNotReached = async function(n) {
console.log("tthis", this)
if (this.HasReachedMaximumBaggageAllowance()) {
angular.element(document.querySelector(".quantity-button")).controller()._registeredControls[1].LuggageDrawersService.OpenLuggageLimitReachedDrawer();
return;
}
this.AddBag(n);
// Returns 1
setTimeout(function(){ console.log("Quantity", this.option.Quantity); }, 2000);
};
Is there a better way I can achieve this, without using settimeout? I have tried async/await/promises, but I still can't seem to find a way to wait for the function to finish loading.
Async/await returns an error - it doesn't like the function this.HasReachedMaximumBaggageAllowance() and throws an error stating this.HasReachedMaximumBaggageAllowance is not a function.
Any tips/ideas would be much appreciated.
I found a solution, I'm using $watch, to watch a key/value, in the this object. And this seems to work:
angular.element(document.querySelector(".quantity-button.plus-button")).controller()._registeredControls[1].Scope.AddBagIfLimitNotReached = function(n) {
let bagCount = this.option.Quantity;
console.log("bagCount", bagCount);
if (this.HasReachedMaximumBaggageAllowance()) {
angular.element(document.querySelector(".quantity-button.plus-button")).controller()._registeredControls[1].LuggageDrawersService.OpenLuggageLimitReachedDrawer();
return;
};
this.AddBag(n);
this.$watch("this.option.Quantity", function (newValue) {
console.log(`Value of foo changed ${newValue}`);
if (newValue > 0) {
document.querySelector(`.luggage-tile-weight-${n.Weight} .tile-title .tick-box`).classList.add("green-tick");
displayGreenTickNoBagSelected();
};
if (newValue === 0) {
document.querySelector(`.luggage-tile-weight-${n.Weight} .tile-title .tick-box`).classList.remove("green-tick");
displayGreenTickNoBagSelected();
};
});
};

Cypress while loop [duplicate]

I have 15 buttons on a page. I need to test each button.
I tried a simple for loop, like
for (var i = 1; i < 15; i++) {
cy.get("[=buttonid=" + i + "]").click()
}
But Cypress didn't like this. How would I write for loops in Cypress?
To force an arbitrary loop, I create an array with the indices I want, and then call cy.wrap
var genArr = Array.from({length:15},(v,k)=>k+1)
cy.wrap(genArr).each((index) => {
cy.get("#button-" + index).click()
})
Lodash is bundled with Cypress and methods are used with Cypress._ prefix.
For this instance, you'll be using the _.times. So your code will look something like this:
Cypress._.times(15, (k) => {
cy.get("[=buttonid=" + k + "]").click()
})
You can achieve something similar to a "for loop" by using recursion.
I just posted a solution here: How to use a while loop in cypress? The control of is NOT entering the loop when running this spec file? The way I am polling the task is correct?
Add this to your custom commands:
Cypress.Commands.add('recursionLoop', {times: 'optional'}, function (fn, times) {
if (typeof times === 'undefined') {
times = 0;
}
cy.then(() => {
const result = fn(++times);
if (result !== false) {
cy.recursionLoop(fn, times);
}
});
});
Then you can use it by creating a function that returns false when you want to stop iterating.
cy.recursionLoop(times => {
cy.wait(1000);
console.log(`Iteration: ${times}`);
console.log('Here goes your code.');
return times < 5;
});
While cy.wrap().each() will work (one of the answers given for this question), I wanted to give an alternate way that worked for me. cy.wrap().each() will work, but regular while/for loops will not work with cypress because of the async nature of cypress. Cypress doesn't wait for everything to complete in the loop before starting the loop again. You can however do recursive functions instead and that waits for everything to complete before it hits the method/function again.
Here is a simple example to explain this. You could check to see if a button is visible, if it is visible you click it, then check again to see if it is still visible, and if it is visible you click it again, but if it isn't visible it won't click it. This will repeat, the button will continue to be clicked until the button is no longer visible. Basically the method/function is called over and over until the conditional is no longer met, which accomplishes the same thing as a for/while loop, but actually works with cypress.
clickVisibleButton = () => {
cy.get( 'body' ).then( $mainContainer => {
const isVisible = $mainContainer.find( '#idOfElement' ).is( ':visible' );
if ( isVisible ) {
cy.get( '#idOfElement' ).click();
this.clickVisibleButton();
}
} );
}
Then obviously call the this.clickVisibleButton() in your test. I'm using typescript and this method is setup in a class, but you could do this as a regular function as well.
// waits 2 seconds for each attempt
refreshQuote(attempts) {
let arry = []
for (let i = 0; i < attempts; i++) { arry.push(i) }
cy.wrap(arry).each(() => {
cy.get('.quote-wrapper').then(function($quoteBlock) {
if($quoteBlock.text().includes('Here is your quote')) {
}
else {
cy.get('#refreshQuoteButton').click()
cy.wait(2000)
}
})
})
}
Try template literals using backticks:
for(let i = 0; i < 3; i++){
cy.get(`ul li:nth-child(`${i}`)).click();
}

E2E tests with multiple pages with information from browser

I am writing an E2E test with protractor. I had to fetch information from the browser and execute a step multiple times.
I am testing one screen which will start when a
User clicks 'Start'
lands on a new page
The workflow below is invoked with count being passed as argument
id the html id does not change. the value changes when queried again after submitting the current form.
for(i = 0 ; i < count ; i++){
console.log("counter is "+i);
element(by('id')).evaluate('value').then(function(v) {
// do some action on UI based on v
element(by('id1')).sendKeys(v+v);
// submit etc.,
// some angular code runs in the frontend.
}
// need to wait since webdriver jumps to the next one without this completing
}
Many blog posts/documentations suggests you cannot use it in a loop, but does not suggest any alternative way to do this.
Any suggestions appreciated.
Never use protractor element statements inside loop: The simple reason is that the webdriverJS (protractor) API is asynchronous. Element statements returns a promise and that promise is in unresolved state while the code below the statements continues to execute. This leads to unpredictable results. Hence, it is advisable to use recursive functions instead of loops.
source: http://engineering.wingify.com/posts/angularapp-e2e-testing-with-protractor/
Edit: updated question with details of workflow.
It is usually not recommended to use a loop when an iteration has an asynchronous call.
The reason is that the first asynchronous calls is executed after the last iteration of the loop when i is already equal to count.
Thus, it makes it difficult to break the loop and to keep track of the value of i.
On way to tackle the issue is to use a recursive function :
var count = 3;
var results = [];
function iterate(i, n) {
if(i < n) {
console.log(`counter is ${i}`);
browser.refresh();
return element(by.id('h-top-questions')).getText().then(function(text) {
results.push(`${i}:${text}`);
return iterate(i + 1, n);
});
}
}
iterate(0, count).then(function(){
console.log("done!", results);
});
But a better way would be to iterate with promise.map on an array sized to the number of iterations:
var count = 3;
protractor.promise.map(Array(count).fill(0), function(v, i) {
console.log(`counter is ${i}`);
browser.refresh();
return element(by.id('h-top-questions')).getText().then(function(text) {
return `${i}:${text}`;
});
}).then(function(results){
console.log("done!", results);
});
You could also keep using a loop. First you'll have to use the let statement to get the value of i in an asynchronous function (ES6).
Then call all the synchronous code with browser.call to synchronize the execution:
var count = 3;
var results = [];
for(let i = 0 ; i < count ; i++){
browser.call(function(){
console.log(`counter is ${i}`);
browser.refresh();
element(by.id('h-top-questions')).getText().then(function(text) {
results.push(`${i}:${text}`);
});
});
}
browser.call(function() {
console.log("done!", results);
});
Looping in protractor works like this
describe('Describe something', function() {
var testParams = [1,2,3,4,5,6,7,8,9,10];
beforeEach( function() {
// ...
});
for (var i = 0; i < testParams.length; i++) {
(function (testSpec) {
it('should do something', function() {
// inside loop
});
})(testParams[i]);
};
});
Edit : I might be mis-understanding your question, but it seems to me you want to complete all(dynamic count) actions on the page, before going to the next one ?
it('should clear old inspections', function() {
inspectieModuleInspectieFixture.getRemoveInspectionButton().count().then(function (value) {
if(value == 0){
console.log('--- no inspections to remove ---');
}
for(var i = 0; i < value; i++){
//global.waitForClickable(inspectieModuleInspectieFixture.getRemoveInspectionButtonList(i+1));
inspectieModuleInspectieFixture.getRemoveInspectionButtonList(i+1).click();
console.log('iteration '+i + 'count '+value )
};
});
global.wait(5000);
}); */
this counts elements on the page and then it performs an action for the ammount of elements it found
In the above example I use containers to hold my elements, so my code remains readable (i.e. inspectieModuleInspectieFixture.getRemoveInspectionButton() holds $(".elementSelectorExample")
There is also a 'global.waitForClickable' commented, that is reffering to a 'time module' I've created that extends the functionality of 'wait', in this case it waits till the element is vissible/clickable.
This is easily mirrored perhaps something like this :
waitForElementNoDisplay: function(element){
return browser.wait(function() {
return element.isDisplayed().then(function(present) {
return !present;
})
});
},
this will make protractor WAIT untill an element is no longer displayed.(Display:none)
If you need to perform some action on every element, it is true, that better to not use loops. Use .map() or .each() or .filter() instead
Still not quite sure what you what to do, but here is example how i am doing similar tasks, when you need to make number of actions depending on data from the page:
class SomePage {
typeValueForEachElement(elements) {
elements.each((elem, index)=> {
elem.getAttribute('value').then(value=> {
elem.sendKeys(value + value)
elem.submit()
})
})
}
}
new SomePage().typeValueForEachElement($$('your locator here'))
Here is api reference that might help
http://www.protractortest.org/#/api?view=ElementArrayFinder.prototype.map
http://www.protractortest.org/#/api?view=ElementArrayFinder.prototype.reduce
http://www.protractortest.org/#/api?view=ElementArrayFinder.prototype.each
http://www.protractortest.org/#/api?view=ElementArrayFinder.prototype.filter

angular `$broadcast` issue - how to fix this?

In my app, I am boradcasting a event for certain point, with checking some value. it works fine But the issue is, later on whenever i am trigger the broadcast, still my conditions works, that means my condition is working all times after the trigger happend.
here is my code :
scope.$watch('ctrl.data.deviceCity', function(newcity, oldcity) {
if (!newcity) {
scope.preloadMsg = false;
return;
}
scope.$on('cfpLoadingBar:started', function() {
$timeout(function() {
if (newcity && newcity.originalObject.stateId) { //the condition not works after the first time means alwasy appends the text
console.log('each time');
$('#loading-bar-spinner').find('.spinner-icon span')
.text('Finding install sites...');
}
}, 100);
});
});
you can deregister the watcher by storing its reference in a variable and then calling it:
var myWatch = scope.$watch('ctrl.data.deviceCity', function(){
if( someCondition === true ){
myWatch(); //deregister the watcher by calling its reference
}
});
if you want to switch logic, just set some variable somewhere that dictates the control flow of the method:
var myWatch = scope.$watch('ctrl.data.deviceCity', function(){
scope.calledOnce = false;
if(!scope.calledOnce){
//... run this the first time
scope.calledOnce = true;
}
else {
// run this the second time (and every other time if you do not deregister this watch or change the variable)
// if you don't need this $watch anymore afterwards, just deregister it like so:
myWatch();
}
})

Protractor: Iterating over options works if debugging but doesn't if not

I have the following test (for the sake of brievity I've removed the page object):
element(by.model("elc.search.placeOfBirth")) //this is a select
element(by.model("elc.search.placeOfBirth")).all(by.tagName("option")).then(function(options) {
for(var i = 0; i < options.length; i++) {
options[i].getText().then(function(text) {
if(text !== "---") {
element(by.model("elc.search.placeOfBirth")).sendKeys(text);
var firstRow = element.all(by.repeater("employee in elc.filtered")).first();
firstRow.all(by.tagName("td")).then(function(cells) {
expect(cells[4].getText()).toBe(text);
});
var lastRow = element.all(by.repeater("employee in elc.filtered")).last();
lastRow.all(by.tagName("td")).then(function(cells) {
expect(cells[4].getText()).toBe(text);
});
}
});
}
});
Let me explain what's happening here. I have a table and a select box above it. The table's 5th column is related to the select combobox and the array I use in ng-repeat for the table is filtered by the value in the combobox. What I wanted to do here is to go over the values in the combobox, select a particular value and make sure the table has that value in the first and last row.
If I but browser.debugger() in the loop this works and the test passes, however if I don't debug the testing seems to go too fast and my table doesn't get updated quickly enough and the tests fail. I'm guessing this is due to the fact that a promise isn't resolved and the code keeps running, but I'm not sure which promise I'm waiting for, as I've also tried to put a .then(function() {...} right after I send the keys to the combobox.
I guess your for loop executes quickly and so the code inside it waiting for promises gets skipped. You can avoid it by executing a function inside the for loop. Update your code to do it -
var someFunction = function(options, i){
//Write your code that was inside your for loop
};
element(by.model("elc.search.placeOfBirth")).all(by.tagName("option")).then(function(options) {
for(var i = 0; i < options.length; i++) {
someFunction(options, i);
}
});
However there's a better solution for this problem. Use the inbuilt loops that protractor has .each() or .map() to get your job done. Here's how -
element(by.model("elc.search.placeOfBirth")).all(by.tagName("option")).each(function(option) {
option.getText().then(function(text) {
if(text !== "---") {
element(by.model("elc.search.placeOfBirth")).sendKeys(text);
var firstRow = element.all(by.repeater("employee in elc.filtered")).first();
firstRow.all(by.tagName("td")).then(function(cells) {
expect(cells[4].getText()).toBe(text);
});
var lastRow = element.all(by.repeater("employee in elc.filtered")).last();
lastRow.all(by.tagName("td")).then(function(cells) {
expect(cells[4].getText()).toBe(text);
});
}
});
});
You may use wait's in between to make sure your DOM is updated before you perform any operation. Hope it solves your issue.

Resources