get element attribute value in Protractor - javascript

I'm writing a Protractor test that has to wait for an element attribute to have a non-empty value and then I want to return that value to the caller function. This has proven to be more difficult to write than I expected!
I am able to correctly schedule a browser.wait() command to wait for the element attribute to have a non-empty value and I have verified that this value is in fact what I am expecting to get inside the callback function, but for some reason, I am not able to return that value outside of the callback function and onto the rest of the test code.
Here is how my code looks like:
function test() {
var item = getItem();
console.log(item);
}
function getItem() {
var item;
browser.wait(function() {
return element(by.id('element-id')).getAttribute('attribute-name').then(function(value) {
item = value;
// console.log(item);
return value !== '';
});
});
return item;
}
I can tell that the order of execution is not as I expect it to be, because when I uncomment the console.log() call inside the callback function, I see the expected value printed out. However, the same call in the test() function prints 'undefined'.
What is going on here? What am I missing? How can I get the attribute value out of the callback function properly?
I appreciate your help.

I would not combine the wait and the getting attribute parts - logically these are two separate things, keep them separate:
browser.wait(function() {
return element(by.id('element-id')).getAttribute("attribute").then(function(value) {
item = value;
// console.log(item);
return value !== '';
});
});
element(by.id('element-id')).getAttribute("attribute").then(function (value) {
console.log(value);
});
Note that, you may simplify the wait condition this way:
var EC = protractor.ExpectedConditions;
var elm = $('#element-id[attribute="expected value"]');
browser.wait(EC.presenceOf(elm), 5000);
elm.getAttribute("attribute").then(function (value) {
console.log(value);
});
Just FYI, you may have solved your current problem with the deferred:
function test() {
getItem().then(function (value) {
console.log(value);
});
}
function getItem() {
var item = protractor.promise.defer();
browser.wait(function() {
return element(by.id('element-id')).getAttribute('attribute').then(function(value) {
var result = value !== '';
if (result) {
item.fulfill(value);
}
return result;
});
});
return item.promise;
}

After doing some more reading about how protractor works with promises and schedules/registers them with the control flow, I found an easier work-around close to the first solution #alecxe provided. Here it goes:
function test() {
var item = getItem().then(function(item) {
console.log(item);
});
}
function getItem() {
return browser.wait(function() {
return element(by.id('element-id')).getAttribute('attribute-name').then(function(value) {
return value;
});
});
}
Since browser.wait() returns a promise itself, it can be chained with another then() inside the caller and this way the right order of execution is guaranteed.

Related

JavaScript Functions , return undefined

Hello Everyone hope you all doing great ,
this is my code , function with name and callback
taking the name to callback function to make return the name and console.log it
if i
function doSomething(name,callback) {
callback(name);
}
function foo(n) {
return n;
}
var val = doSomething("TEST",foo);
console.log(val);
i got undefined .
if i
function doSomething(name,callback) {
callback(name);
}
function foo(n) {
console.log(n);
}
var val = doSomething("TEST",foo);
that works fine and console.log the TEST name .
So why the return not working ?
thanks
Your doSomething() function doesn't return anything, which means an assignment using it will be undefined. But, that's not really the problem here.
The underlying problem is that you seem to be mixing two different data processing patterns here: if you're writing purely synchronous code, then use returning functions (which immediately return some value). If you need asynchronous code, then use a callback (which will "eventually" do something). Mixing those two patterns is a recipe for problems and frustration:
Either:
don't name your function a "callback", and have it return its processed value, or
make the callback responsible for doing whatever it is you were going to do with val.
Case 1:
function doSomething(data, processor) {
return processor(data);
}
function passThrough(v) { return v; }
var val = doSomething("test", passThrough);
// immediately use "val" here in for whatever thing you need to do.
Case 2:
function doSomething(data, callback) {
// _eventually_ a callback happens - for instance, this
// function pulls some data from a database, which is one
// of those inherently asynchronous tasks. Let's fake that
// with a timeout for demonstration purposes:
setTimemout(() => callback(data), 500);
}
function handleData(val) {
// use "val" here in for whatever thing you need to do. Eventually.
}
doSomething("test", handleData);
And if you want to go with case 2, you really want to have a look at "Promises" and async/await in modern Javascript, which are highly improved approaches based on the idea of "calling back once there is something to call back about".
2021 edit: a third option since original writing this answer is to use the async/await pattern, which is syntactic sugar around Promises.
Case 3:
async function doSomething(input) {
// we're still _eventually_ returning something,
// but we're now exploiting `async` to wrap a promise,
// which lets us write normal-looking code, even if what
// we're really doing is returning a Promise object,
// with the "await" keyword auto-unpacking that for us.
return someModernAsyncAPI.getThing(input);
}
function handleData(val) {
// ...
}
async function run() {
const data = await doSomething("test");
handleData(data);
}
run();
function doSomething(name,callback) {
callback(name);
}
function foo(n) {
console.log(n);
return n;
}
var val = doSomething("TEST",foo);
Take a look at above code. When you call doSomething, which internally executes foo it prints on the console because thats what console.log is for. However, after this statement it returns n as well which then is received in doSomething. But its not being returned. To put it simply, what you are mainly doing is
function doSomething(name,callback) {
const returnValue = callback(name);
}
If you call the above method, it will return undefined. To make it return correct value, you have to call "return returnValue". Similary you have to say
return callback(name)
Hope this helps.
Happy Learning
Inorder to assign the returning value/object of a function(in this case doSomething, it should have a return statement. Else the function returns nothing, so when you assign that to val, it is still undefined.
So you modify your code like this:
function doSomething(name,callback) {
return callback(name);
}
function foo(n) {
return n;
}
var val = doSomething("TEST",foo);
console.log(val);
undefined is implicitly returned if you don't have a return in your function.
when you call var val = doSomething("TEST",foo), you are aligning the return value of doSomething to val, which is undefined.
function doSomething(name,callback) {
return callback(name);
}
function foo(n) {
return n;
}
var val = doSomething("TEST",foo);
console.log(val);

Having a difficult time understanding callback functions

I'm new to JavaScript and i'm having difficulties understanding callback functions.
I have a function below that returns an array and then another function that calls the first function. Problem is, the array in the second function is always undefined, even though in the first function is returns an array of objects.
function getItems() {
$.get(url,
function(data) {
var obj = $.parseJSON(data);
var itemArr = $.map(obj, function(ele) { return ele; })
return itemArr;
});
}
function alertTest() {
var items = getItems();
alert(items);
}
I understand that the first function is asynchronous and so that the alert in the second function is called before the returned array, which causes the second function to alert an undefined object.
I'm aware there is quite some documentation around this but i'm having trouble understanding how it works. Could someone show me the changes i would need to make so that the alertTest function returns the populated array after the getItems function has been called?
Thanks in advance!
$.get is an async function. which means the callback function is invoked when the is hit and the response is returned inside .
Now return itemArr is actually returned by the callback function and
getItems() doesn't actually return anything and hence it is always undefined.
For your code to work,
function getItems() {
$.get(url,
function(data) {
var obj = $.parseJSON(data);
var itemArr = $.map(obj, function(ele) { return ele; })
alertTest(itemArr);
return itemArr;
});
}
this would call alertTest function.
One way to do this would be to use JS Promises like so
function getItems() {
return Promise.resolve(
$.get(url,
function (data) {
var obj = $.parseJSON(data);
var itemArr = $.map(obj, function (ele) {
return ele;
})
return itemArr;
}));
}
function alertTest() {
var items = getItems().then(function (items) {
alert(items);
});
}
It is a hard one to get your head around. Promises allow you to write code that runs in a sequence like sync code.
You could insert callbacks into the function and have that call when the get request finishes like shown in the other answer by Rhea but this is a cleaner way to do this and Promises are now part of the Javascript language.
function getItems() {
$.get(url, function(data) {
var obj = $.parseJSON(data);
var itemArr = $.map(obj, function(ele) { return ele; })
alertTest(itemArr);
});
}
function alertTest(items) {
alert(items);
}
getItems();

Function doesn't return anything (async?)

I'm trying to add XPATH evaluation into my form. When user fills XPATH, it's evaluated on a server using AJAX and then return true or false.
The problem is that this function seems to return undefined allways. I suppose it's because of asynchronious behaviour of JS so I used $.when but it didn't helped.
function evalXpath(xpath) {
var test = $.post('/api/test-xpath/', {'xpath': xpath});
test.done(function (data) {
console.log('BEFORE RETURN '+Boolean(data['success']));
return Boolean(data['success']);
})
}
$(document).ready(function () {
$('#id_xpath').on('change', function () {
var xpath = $("#id_xpath").val();
$.when(evalXpath(xpath)).done(function (evaluated) {
console.log('RETURNED '+evaluated);
$('#xpath-valid').text(evaluated ? 'VALID' : 'INVALID');
});
});
});
The console output (as you can see, it's still asynchronious):
Do you have any ideas?
You're really close. A couple of things:
You forgot to return the promise out of evalXpath.
To get proper promise value chaining (e.g., in order for your value from the callback inside evalXpath to then become the resolution value of the promise it returns), use then, not done.
Then when using evalXpath, there's no need for $.when.
So:
function evalXpath(xpath) {
var test = $.post('/api/test-xpath/', {'xpath': xpath});
return test.then(function (data) {
// ^^^^^^ ^^^^
console.log('BEFORE RETURN '+Boolean(data['success']));
return Boolean(data['success']);
})
}
// ...
evalXpath(xpath).then(function (evaluated) {
// ^^^^ (this one could be `done`, but let's be consistent)

Cannot get the current value returned by $http.get when setting $scope variable, only the previous value

I have an angular view that has a table of rows consisting of a select list and an text box. When a select list index is changed, I need to update the corresponding text box on the same row with a lookup value from the database. I am using ng-Change on the select list to call a $scope function that utilizes $http.get to make the call through an ActionMethod. I have tried this in a million ways, and finally was able to extract a value from the $http.get function by assigning it to a scope variable, but I only ever get the value of the previous lookup triggered by the selected index change, not the current one. How can I get a value real-time? I understand it is asynchronous, so I know the nature of the problem. How do I work around it? Current state of my .js:
$scope.EntityId = null;
$scope.EntityNameChanged = function (item, block) {
for (var i = 0; i < block.length; i++)
{
if (item.Value == block[i].Name.Value) {
$scope.GetEntityId(item.Value);
block[i].Id = $scope.EntityId;
}
}
}
$scope.GetEntityId = function(name) {
$http.get("EntityId", { params: { EntityName: name } }).then(function success(response) {
$scope.EntityId = response.data[0].Value;
});
};
The GetEntityID function should return a promise
function GetEntityId(name) {
//save httpPromise
var p = $http.get("EntityId", { params: { EntityName: name } });
//return derived promise
return p.then(function onSuccess(response) {
//return chained data
return response.data[0].Value;
});
};
Then use an IIFE in the for loop.
$scope.EntityNameChanged = function (item, block) {
for (var i = 0; i < block.length; i++) {
//USE IIFE to hold value of i
(function IIFE(i) {
if (item.Value == block[i].Name.Value) {
//save promise
var p = GetEntityId(item.Value);
//extract value from promise
p.then(function onSuccess(Value) {
block[i].Id = Value;
});
}
})(i);
}
}
Because the onSuccess function gets invoked asynchronously after the for loop completes, an IIFE closure is necessary to preserve the value of i until after the data is returned from the server.
Your GetEntityId function is not async, even though it makes an async request. by the time it sets $scope.EntityId, the for loop has already exited.
You can't actually queue up async calls like this, because each one of them is trying to share a value outside the loop that could be set by any other iteration, so one item in the loop might get another item's return value.
Instead, you should return the promise back to the loop, and perform your .then in the loop. Something like the following:
$scope.EntityNameChanged = function(item, block) {
for (var i = 0; i < block.length; i++) {
if (item.Value == block[i].Name.Value) {
$scope.GetEntityId(item.Value)
.then(function success(response) {
block[i].Id = response.data[0].Value;
});
}
}
}
$scope.GetEntityId = function(name) {
return $http.get("EntityId", {
params: {
EntityName: name
}
});
};
(note this is untested, but should do what you expect).
To prompt Angular to update the value of the scope on its $watch loop, call $scope.apply() after assignment. This should bring you to the most recent value.
EDIT: This answer was wrong. It was a $http get request, which already uses $apply.
What you need to do is put the request inside a factory and return a promise. Require the factory in your controller.
app.factory('getData', function ($http, $q){
this.getlist = function(){
return $http.get('mylink', options)
.then(function(response) {
return response.data.itemsToReturn;
});
}
return this;
});
app.controller('myCtrl', function ($scope, getData){
app.getData()
.then(function(bar){
$scope.foo = bar;
});
});

Bluebird Promise Scope

I have just started using promises in attempt to cleanup some 'callback hell'.
I've decided on trying bluebird and I am running it in the browser but immediately ran into scoping problems.
Is there a way of setting the thisArg in a new Promise? The below example shows that the 'this' value inside the promise resolver is set to the browser window, but I'd like it set to the surrounding scope so I can easily access member variables.
I noticed there is a .bind() method but it only scopes the 'then()' method, not the promise. I also realize I can have 'var me = this' just before the promise and use closure, but I wanted to avoid it if possible.
function MyObject() {
this.value = 7;
}
MyObject.prototype.getValue = function () {
return new Promise(function (resolve) {
// some request/processing that takes a long time
var result = Ajax.request(...);
resolve({
value: this.value,
result: result
});
// 'this' is set to window instead of the instance,
// resulting in this.value as undefined
});
}
var obj = new MyObject();
obj.getValue().then(function (value) {
console.log(value); // preferably outputs 7
})
No, there is not. You can of course use the default approaches, but you shouldn't need to.
When doing heavy processing and getting back the value asynchronously, you want to get a promise for the value. You don't need to set the result value as a property of the original instance.
MyObject.prototype.getValue = function () {
return new Promise(function(resolve) {
// lots of processing to make a `value`
resolve(value); // no `this` at all!
});
};
In case you want to synchronously get the .value that you had set on the instance, you don't need the Promise constructor. Just use Promise.resolve to make a promise for an existing value:
MyObject.prototype.getValue = function () {
// TODO: lots of processing
return Promise.resolve(this.value);
};
Or, in your case, even Promise.method:
// TODO: lots of processing
MyObject.prototype.getValue = Promise.method(function () {
return this.value;
});
This is more a comment then an answer, as it is primary opinion based.
In the rar situations where I need this it would look like this in my code:
Ajax.requestAsync in this case would be a promisifyed version of Ajax.request.
I know this might just move your problem to another place.
MyObject.prototype.getValue = function () {
return Ajax.requestAsync(...)
.bind(this)
.then(function(result) {
return {
value: this.value,
result: result
}
});
}
Or something like this:
MyObject.prototype.getValue = function () {
var returnValue = {
value: this.value
};
return Ajax.requestAsync(...)
.then(function(result) {
returnValue.result = result;
return returnValue;
});
}
A rarely use such constructs:
MyObject.prototype.getValue = function () {
return Promise.all([this, Ajax.requestAsync(...)])
.spread(function(object, result) {
return {
value: object.value,
result: result
};
});
}

Categories