I am fairly new to JavaScript and Protractor. I have simple task in my test that I am unable to complete.
Check on available Tab on web page.
Check if element is visible on web page.
a) If Yes, return deffered.fullfil(true)
b) If No,
- Click on In-Progress Tab
- Click on Available Tab.
- Go to Step 1.
I am trying to do this recursively and below is my code. It is printing Element found but never exits the function after that and times out.
var check_availability = function(counter, totalCount, element){
var deferred = potractor.promise.defer()
if(counter <= totalCount){
browser.wait(function(){
browser.wait(EC.visibilityOf(element),2000)
return element
}).then(function(success){
console.log('Element found.')
return deferred.fulfill(true)
}, function(err){
inprogressTab.click()
.then(() => availableTab .click())
.then(() => check_availability (counter+1 , totalCount, element))
})
} else{
return deferred.reject(false)
}
return deferred.promise
}
PS: This is a sample code that I am using, corrected some spelling mistakes and syntax.
I see several syntax errors on your shared code. Below i tried to fix those errors and provided the expected behavior but still don't know from where the inprogressTab is coming from.
const check_availibility = function(counter, totalCount, element) {
const deferred = protractor.promise.defer();
if (counter <= totalCount) {
browser
.wait(() => browser.wait(EC.visibilityOf(element), 2000))
.then(
element => {
console.log("Element found.");
return deferred.fulfill(true);
},
err => {
inprogressTab
.click()
.then(() => availableTab.click())
.then(() => check_availibility(counter + 1, totalCount, element));
}
);
} else {
return deferred.reject(false);
}
return deferred.promise;
};
First, this code has spelling mistakes. "deferred" is with one "f", and so the call promise.deffer() will give a runtime error. It should be .defer(). Also rpotractor is misspelled. Your code could not even run.
Secondly, you are using an anitpattern: there is no need to create a promise/deferred, when you already get a promise object from browser.wait. Just return that one (or one returned from a then chain).
Also, return element is a wrong return value. It executes synchronously, so before the waiting is over, and the then chain will kick in too soon. Instead, make sure to return the promise that browser.wait returns.
You could do something like this:
var check_availibility = function(counter, totalCount, element){
if (counter <= totalCount) {
return browser.wait(function () {
return browser.wait(EC.visibilityOf(element), 2000)
// ^^^^^^
}).then(function () {
console.log('Element found.')
return true;
}).catch(function (err) {
return inprogressTab.click()
// ^^^^^^
.then(() => availableTab.click())
.then(() => check_availibility(counter+1 , totalCount, element))
})
} else {
return protractor.promise.rejected(false);
}
}
Note that JavaScript has native Promise support since EcmaScript2015, so instead of using protractor.promise, you could just use Promise.
Related
I have a problem with if(.isDisplayed()) else if (.isDisplayed()) else condition.
The isDisplayed() function doesn't work for this condition, it always enters in the first if block even the powderRinseStartButton is not in the DOM.
if (powderRinseStartButton != null && powderRinseStartButton.isDisplayed() && powderRinseStartButton.ispresent()) {
powderRinseStartButton.click();
} else if (brewerRinseStartButton != null && brewerRinseStartButton.isDisplayed() && brewerRinseStartButton.ispresent()) {
brewerRinseStartButton.click();
} else {
fn.click();
}
if I put the first or second piece of code, it works fine
browser.wait(() => {
return brewerRinseStartButton.isDisplayed().then(() => {
browser.wait(EC.visibilityOf(brewerRinseStartButton), delay).then(() =>
{
browser.wait(EC.elementToBeClickable(brewerRinseStartButton), delay).then(() =>
{
expect(EC.elementToBeClickable(brewerRinseStartButton)).toBeTruthy();
brewerRinseStartButton.click().then(() =>
{
browser.wait(EC.visibilityOf(maintenanceText), 240000,
'The Maintenance Text should be visible within 240s');
expect(maintenanceText.isDisplayed()).toBeTruthy();
});
});
});
});
// return powderRinseStartButton.isDisplayed().then(() =>
// {
// browser.wait(EC.visibilityOf(powderRinseStartButton), delay).then(() =>
// {
// browser.wait(EC.elementToBeClickable(powderRinseStartButton), delay).then(() =>
// {
// expect(EC.elementToBeClickable(powderRinseStartButton)).toBeTruthy();
// powderRinseStartButton.click().then(() =>
// {
// browser.wait(EC.visibilityOf(maintenanceText), 240000,
// 'The Maintenance Text should be visible within 240s');
// expect(maintenanceText.isDisplayed()).toBeTruthy();
// });
// });
// });
// });
}, 5000)
.then(() => {
console.log('return true')
return true;
}, () => {
console.log('false');
browser.wait(EC.visibilityOf(fn), delay).then(() => {
browser.wait(EC.elementToBeClickable(fn), delay).then(() => {
expect(EC.elementToBeClickable(fn)).toBeTruthy();
fn.click();
});
});
});
I want to make a condition if brewerRinseStartButton is showed than click on brewerRinseStartButton, elseif powderRinseStartButton is showed than click on powderRinseStartButton else dosomething.
I solved this problem.
The isDisplayed()
#return
A promise that will be resolved with whether this element is currently visible on the page.
Wait for all promises to be resolved, or for any to be rejected.
let failHandler = ()=>
{
browser.wait(EC.visibilityOf(fn), delay).then(() =>
{
browser.wait(EC.elementToBeClickable(fn), delay).then(() =>
{
expect(fn.isDisplayed()).toBeTruthy();
expect(EC.elementToBeClickable(fn)).toBeTruthy();
fn.click();
});
});
};
brewerRinseStartButton.isDisplayed().then(()=>
{
fnBrewer();
},()=>
{
powderRinseStartButton.isDisplayed().then(()=>
{
fnPowder();
},()=>
{
failHandler();
});
});
IsDisplayed() function returns promise, so there are two ways to handle it. One as you also attempted to use then on returning promise object and perform action afterwards.
Second one is most correct and clean way to use async/await for handling promises
async clickStartButton() {
if(powderRinseStartButton != null && await
powderRinseStartButton.isDisplayed() && await
powderRinseStartButton.ispresent())
{
await powderRinseStartButton.click();
}
else if(brewerRinseStartButton != null && await
brewerRinseStartButton.isDisplayed() && await
brewerRinseStartButton.ispresent())
{
await brewerRinseStartButton.click();
}
else
{
await fn.click();
{}
}
Note: I've used my mobile keypad for typing so please ignore the code identation
I would probably attempt to do this by using the or functionality available in xpath locators. The logic is that you provide a single locator which contains the locators required to locate both objects. If the first locator is found it will be used, if the second is found it will be used. If neither are found then you can use your ele.isDisplayed() check and run your else condition on it.
let requiredEle = element(by.xpath('//button[#class="locator1" or #class="locator2"]');
requiredEle.isDisplayed().then(function(wasDisplayed){
if(wasDisplayed){
requiredEle.click();
} else {
//Your else actions
}
})
* not tested
I would also advise using isPresent instead of isDisplayed if you can as isDisplayed throws an exception if the element is not present and I'm not exactly sure how that will behave in the above if statement.
I have tried with this code. I hope this should solve your problem. I have refactored WaitForElement code seperately so that it can be used in both the cases.
async WaitForElement(element, timeout = 30000) {
try {
await browser.wait(function () {
return element.isDisplayed()
.then((isDisplayed) => isDisplayed, () => false);
}, timeout);
await browser.wait(function () {
return element.isPresent()
.then((isPresent) => isPresent, () => false);
}, timeout);
return true;
}
catch (e) {
console.log(e.message);
return false;
}
}
async clickStartButton() {
// powderRinseStartButton = element(define locator)
//brewerRinseStartButton = element(define locator)
if (await this.WaitForElement(this.powderRinseStartButton)) {
await powderRinseStartButton.click();
} else if (await this.WaitForElement(this.brewerRinseStartButton)) {
await brewerRinseStartButton.click();
} else {
//await fn.click();
}
}
Explanation:
First element.isDisplayed() function returns Promise object so if we put it into If condition then it will always return true that's why it will always pass through first If loop.
Second point is that browser.wait understand true/false, hence we have to resolve element.isDiplayed promise so that it never through exception and we can wait until timeout.
Try the below one. This should definitely work for you.
const powderRinseStartButton= element(by.id('start_powder_rinse');
const brewerRinseStartButton = element(by.id('start_brewer_rinse');
if (await powderRinseStartButton.ispresent()) {
await powderRinseStartButton.click();
} else if (await brewerRinseStartButton.ispresent()) {
await brewerRinseStartButton.click();
} else {
await fn.click();
}
if the button takes some time to load. try adding sleep before if.
Hope it helps you. Please share the error if the approach fails.
One hack you can try for this scenario is using .then() .catch() chain of promises here instead of if else. So in then you try for clicking first element button And in catch() you do not reject or throw error but instead you click on the second element button.
So in the function you can return like below:
return brewerRinseStartButton.isDisplayed()
.then(() => brewerRinseStartButton.click())
.catch(() => {
return powderRinseStartButton.isDisplayed()
.then(() => powderRinseStartButton.click());
});
Note: I've also used my mobile keypad for typing so please ignore the code identation. Also I assumed that first you want to try clicking (if found) on brewerRinseStartButton and then you want to try clicking on powderRinseStartButton.
I am still new to Promises and async coding in JavaScript. I am trying to create a function that returns a promise that iterate through an array of objects with a setTimeout. On each element, I will pass it to another function that returns a Promise. If the element doesn't satisfy a condition, I put it into another array and pass that new array into the function for a recursive call 10 more times until it satisfy the condition. Here is the code:
const promiseFunc = (item) => {
return new Promise((resolve, reject) => {
// Do something
if (some_kind_of_error) {
return reject(the_error);
} else {
return resolve({
itemName: item.name,
status: (item.isComplete === 'complete')
});
}
});
};
const func2 = (listOfItems, count) => {
return new Promise((resolve, reject) => {
if (count > 10) {
reject(new Error("Too many attempts."));
}
setTimeout(() => {
const newList = [];
listOfItems.forEach(item => {
promiseFunc(item)
.then(result => {
if(result.isCompleted !== true) {
newList.push(item);
}
});
});
if (newList.length === 0) {
return resolve(true);
} else {
console.log('Calling func2 again');
return func2(newList, count+1);
}
}, 1000);
});
};
The problem is that when I run the func2 function, I always get true even if it is suppose to recurse.
When I tried to log things out, I notice that the message Calling func2 again was not logged out in the terminal. This means that no matter what, the condition for checking newList will always be empty hence it is always resolving true and never going to the else statement.
Can someone please explain why this is the current behavior? How do I make it so that my func2 will wait for the execution of if (newList.length === 0) until my forEach loop is done?
I would like to walk a database table using Promises to get the data from each step synchronously. I think my code should look something like:
function get_next_id(the_id) {
return new Promise(function(resolve) {
connection.query(get_parent_query, [ the_id ], function (e, r, f) {
resolve(r[0].from_visit);
});
});
}
var page_id = 60239;
while (page_id > 0) {
get_next_id(page_id).then((i) => page_id = i);
}
The problem with this code is that the loop iterates immediately without waiting for the then() to complete.
In this answer the poster suggests either using Promise.race() or abandoning Promise altogether in favor of async.
May use async / await:
(async function(){
var pageId = 60239;
while (page_id > 0) {
pageId = await get_next_id(pageId);
}
})()
or use indirect recursion:
(function next(pageId){
if(pageId <= 0) return;
get_next_id(pageId).then(next);
})(60239);
I don't understand why you want to get a bunch of id's but not do anything with the results. Your original function was almost there but you should reject with the error and the results so far if something goes wrong.
And resolve with all the results if everything goes right:
function get_next_id(the_id,results=[]) {
return new Promise(function (resolve,reject) {
connection.query(get_parent_query, [the_id], function (e, r, f) {
if(e){
//reject if something goes wrong with error and
// what has been done so far
reject([e,results]);
return;
}
resolve(r);
});
})
.then(function (r){
if(r[0].from_visit===0){
return results;
}
//recusively call unless id is 0
return get_next_id(r[0].from_visit,results.concat(r))
});
}
get_next_id(22)
.then(
results=>console.log("got results:",results)
,([error,resultsSoFar])=>console.error(
"something went wrong:",error,
"results before the error:",resultsSoFar
)
);
I've created this a couple days ago in which i needed help regarding how to add custom properties to a said document.
First of all, I'm running Word 1701(7766.2047).
Let's say I have a method In which I return a said custom property. First I'd check if the custom property has been created already. I would do this with a simple getItemOrNullObject(key) and..
If returns null Then simply create it AND return it
Else return it
It is of my understanding that I need to do a return context.sync().then for the object get actually loaded with data? Am I doing too much return context.sync() calls for nothing?
Word.run(function(context) {
var customDocProps = context.document.properties.customProperties;
context.load(customDocProps);
return context.sync()
.then(function() {
var temp = customDocProps.getItemOrNullObject("X");
return context.sync()
.then(function() {
if (!temp) {
context.document.properties.customProperties.add("X", 1234);
temp = customDocProps.getItemOrNullObject("X");
return context.sync()
.then(function() {
return temp;
});
} else {
return temp;
}
});
});
});
The following code throws me an 'ReferenceError: 'Word' is undefined' at start but if I debug it it runs before it breaks
var customDocProps = context.document.properties.customProperties;
context.load(customDocProps);
return context.sync().{....}
Also have one more question. Say I want to update my custom property, would :
Word.run(function (context) {
context.document.properties.customProperties.add("X", 56789);
return context.sync();
});
override the old value with the new one?
If you read this far thank you! Any help is appreciated.
Cheers!
Thanks for asking this question.
Your code is correct except for one minor detail: All the *getItemOrNullObject methods do NOT return a JavaScript null, so your "if (!temp)" statement will not work as you expect. If you want to validate existence you need to call if(temp.isNullObject) instead.
Also a couple of suggestions:
customProperties.add() semantics is that if the property does exist, it will be replaced. So, If you want to create or change the property you don't need to check if it exists or not. If you want to read its current value you do. This answers your 2nd question.
I have a simplified and more efficient proposal for your code, if you are interested on loading a single property.
Word.run(function (context) {
var myProperty = context.document.properties.customProperties.getItemOrNullObject("X");
context.load(myProperty);
return context.sync()
.then(function () {
if (myProperty.isNullObject) {
//this means the Custom Property does not exist....
context.document.properties.customProperties.add("X", 1234);
console.log("Property Created");
return context.sync();
}
else
console.log("The property already exists, value:" + myProperty.value);
})
})
.catch(function (e) {
console.log(e.message);
})
We will update the documentation as this seems to be confusing.
Thanks!
I use these function to get or set custom properties
// sets a custom property on the current Word file
function setDocProperty (propName, propValue, callback) {
Word.run(context => {
context.document.properties.customProperties.add(propName, propValue)
return context.sync()
.then(() => {
callback(null)
})
.catch(e => {
callback(new Error(e))
})
})
}
// gets a custom property from the current Word file
function getDocProperty (propName, callback) {
Word.run(context => {
var customDocProps = context.document.properties.customProperties
// first, load custom properties object
context.load(customDocProps)
return context.sync()
.then(function () {
// now load actual property
var filenameProp = customDocProps.getItemOrNullObject(propName)
context.load(filenameProp)
return context.sync()
.then(() => {
callback(null, filenameProp.value)
})
.catch(err => {
callback(new Error(err))
})
})
.catch(err => {
callback(new Error(err))
})
})
}
You use them like this:
setDocProperty('docId', 28, () => {
console.log('property set')
})
getDocProperty('docId', (err, value) => {
if (err) {
console.log('Error getting property', err)
} else {
console.log('the property is ' + value)
}
})
I'm trying to make an asynchronous loop with native ES6 promises It kind of works, but incorrectly. I suppose I made a huge mistake somewhere and I need someone to tell me where it is and how it's done correctly
var i = 0;
//creates sample resolver
function payloadGenerator(){
return function(resolve) {
setTimeout(function(){
i++;
resolve();
}, 300)
}
}
// creates resolver that fulfills the promise if condition is false, otherwise rejects the promise.
// Used only for routing purpose
function controller(condition){
return function(resolve, reject) {
console.log('i =', i);
condition ? reject('fin') : resolve();
}
}
// creates resolver that ties payload and controller together
// When controller rejects its promise, main fulfills its thus exiting the loop
function main(){
return function(resolve, reject) {
return new Promise(payloadGenerator())
.then(function(){
return new Promise(controller(i>6))
})
.then(main(),function (err) {
console.log(err);
resolve(err)
})
.catch(function (err) {
console.log(err , 'caught');
resolve(err)
})
}
}
new Promise(main())
.catch(function(err){
console.log('caught', err);
})
.then(function(){
console.log('exit');
process.exit()
});
Now the output:
/usr/local/bin/iojs test.js
i = 1
i = 2
i = 3
i = 4
i = 5
i = 6
i = 7
fin
error: [TypeError: undefined is not a function]
error: [TypeError: undefined is not a function]
error: [TypeError: undefined is not a function]
error: [TypeError: undefined is not a function]
error: [TypeError: undefined is not a function]
error: [TypeError: undefined is not a function]
error: [TypeError: undefined is not a function]
caught [TypeError: undefined is not a function]
exit
Process finished with exit code 0
The good part: it reaches the end.
The bad part: it catches some errors and I don't know why.
Any helper function with promise looping I have seen actually made it much worse than what you can do out of the box with recursion.
It is a little nicer with .thenReturn but yeah:
function readFile(index) {
return new Promise(function(resolve) {
setTimeout(function() {
console.log("Read file number " + (index +1));
resolve();
}, 500);
});
}
// The loop initialization
Promise.resolve(0).then(function loop(i) {
// The loop check
if (i < len) { // The post iteration increment
return readFile(i).thenReturn(i + 1).then(loop);
}
}).then(function() {
console.log("done");
}).catch(function(e) {
console.log("error", e);
});
See it in jsfiddle http://jsfiddle.net/fd1wc1ra/
This is pretty much exactly equivalent to:
try {
for (var i = 0; i < len; ++i) {
readFile(i);
}
console.log("done");
} catch (e) {
console.log("error", e);
}
If you wanted to do nested loops it is exactly the same:
http://jsfiddle.net/fd1wc1ra/1/
function printItem(item) {
return new Promise(function(resolve) {
setTimeout(function() {
console.log("Item " + item);
resolve();
}, 500);
});
}
var mdArray = [[1,2], [3,4], [5,6]];
Promise.resolve(0).then(function loop(i) {
if (i < mdArray.length) {
var array = mdArray[i];
return Promise.resolve(0).then(function innerLoop(j) {
if (j < array.length) {
var item = array[j];
return printItem(item).thenReturn(j + 1).then(innerLoop);
}
}).thenReturn(i + 1).then(loop);
}
}).then(function() {
console.log("done");
}).catch(function(e) {
console.log("error", e);
});
If all you're trying to do is count to 7 with promises, then this will do it:
function f(p, i) {
return p.then(function() {
return new Promise(function(r) { return setTimeout(r, 300); });
})
.then(function() { console.log(i); });
}
var p = Promise.resolve();
for (var i = 0; i < 8; i++) {
p = f(p, i);
}
p.then(function() { console.log('fin'); })
.catch(function(e) { console.log(e.message); });
Looping with promises is hard, because it's almost impossible not to fall into JavaScript's closures in a loop trap, but it is doable. The above works because it pushes all use of .then() into a sub-function f of the loop (i.e. away from the loop).
A safer solution, that I use, is to forgo loops altogether and seek out patterns like forEach and reduce whenever I can, because they effectively force the sub-function on you:
[0,1,2,3,4,5,6,7].reduce(f, Promise.resolve())
.then(function() { console.log('fin'); })
.catch(function(e) { console.log(e.message); });
here f is the same function as above. Try it.
Update: In ES6 you can also use for (let i = 0; i < 8; i++) to avoid the "closures in a loop" trap without pushing code into a sub-function f.
PS: The mistake in your example is .then(main(), - it needs to be .then(function() { return new Promise(main()); }, but really, I think you're using the pattern wrong. main() should return a promise, not be wrapped by one.
Try logging err.stack instead of just err when catching promise errors.
In this case, it looks like resolve and reject are not defined within the anonymous function that gets return from main after the initial iteration is complete. I can't totally follow your control flow, but that seems to make sense - after the 7 iterations are complete, there should no longer be any new promises. However, it seems like the code is still trying to run like there are more promises to resolve.
Edit: This is the problem .then(main(),function (err) {. Invoking main on its own will cause resolve and reject inside the anonymous function to be undefined. From the way I read it, main can only be invoked as an argument to the Promise constructor.
I had a similar need and tried the accepted answer, but I was having a problem with the order of operations. Promise.all is the solution.
function work(context) {
return new Promise((resolve, reject) => {
operation(context)
.then(result => resolve(result)
.catch(err => reject(err));
});
}
Promise
.all(arrayOfContext.map(context => work(context)))
.then(results => console.log(results))
.catch(err => console.error(err));
I've looked around for various solutions too and couldn't find one that satisfied me, so I ended up creating my own. Here it is in case it's useful to someone else:
The idea is to create an array of promise generators and to give this array to a helper function that's going to execute the promises one after another.
In my case the helper function is simply this:
function promiseChain(chain) {
let output = new Promise((resolve, reject) => { resolve(); });
for (let i = 0; i < chain.length; i++) {
let f = chain[i];
output = output.then(f);
}
return output;
}
Then, for example, to load multiple URLs one after another, the code would be like this:
// First build the array of promise generators:
let urls = [......];
let chain = [];
for (let i = 0; i < urls.length; i++) {
chain.push(() => {
return fetch(urls[i]);
});
}
// Then execute the promises one after another:
promiseChain(chain).then(() => {
console.info('All done');
});
The advantage of this approach is that it creates code that's relatively close to a regular for loop, and with minimal indentation.